| Trees | Indices | Help |
|
|---|
|
|
1 """Widgets dealing with patient demographics."""
2 #============================================================
3 # $Source: /cvsroot/gnumed/gnumed/gnumed/client/wxpython/gmDemographicsWidgets.py,v $
4 # $Id: gmDemographicsWidgets.py,v 1.175 2010/02/07 15:13:02 ncq Exp $
5 __version__ = "$Revision: 1.175 $"
6 __author__ = "R.Terry, SJ Tan, I Haywood, Carlos Moro <cfmoro1976@yahoo.es>"
7 __license__ = 'GPL (details at http://www.gnu.org)'
8
9 # standard library
10 import time, string, sys, os, datetime as pyDT, csv, codecs, re as regex, psycopg2, logging
11
12
13 import wx
14 import wx.wizard
15
16
17 # GNUmed specific
18 if __name__ == '__main__':
19 sys.path.insert(0, '../../')
20 from Gnumed.pycommon import gmDispatcher, gmI18N, gmMatchProvider, gmPG2, gmTools, gmCfg
21 from Gnumed.pycommon import gmDateTime, gmShellAPI
22 from Gnumed.business import gmDemographicRecord, gmPerson, gmSurgery
23 from Gnumed.wxpython import gmPlugin, gmPhraseWheel, gmGuiHelpers, gmDateTimeInput
24 from Gnumed.wxpython import gmRegetMixin, gmDataMiningWidgets, gmListWidgets, gmEditArea
25 from Gnumed.wxpython import gmAuthWidgets, gmCfgWidgets
26 from Gnumed.wxGladeWidgets import wxgGenericAddressEditAreaPnl, wxgPersonContactsManagerPnl, wxgPersonIdentityManagerPnl
27 from Gnumed.wxGladeWidgets import wxgNameGenderDOBEditAreaPnl, wxgCommChannelEditAreaPnl, wxgExternalIDEditAreaPnl
28
29
30 # constant defs
31 _log = logging.getLogger('gm.ui')
32
33
34 try:
35 _('dummy-no-need-to-translate-but-make-epydoc-happy')
36 except NameError:
37 _ = lambda x:x
38
39 #============================================================
40 # country related widgets / functions
41 #============================================================
43
44 if parent is None:
45 parent = wx.GetApp().GetTopWindow()
46
47 countries = gmDemographicRecord.get_countries()
48
49 gmCfgWidgets.configure_string_from_list_option (
50 parent = parent,
51 message = _('Select the default region for new persons.\n'),
52 option = 'person.create.default_country',
53 bias = 'user',
54 choices = [ (c['l10n_country'], c['code']) for c in countries ],
55 columns = [_('Country'), _('Code')],
56 data = [ c['country'] for c in countries ]
57 )
58 #============================================================
60
62
63 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
64
65 context = {
66 u'ctxt_zip': {
67 u'where_part': u'and zip ilike %(zip)s',
68 u'placeholder': u'zip'
69 }
70 }
71 query = u"""
72 select code, name from (
73 select distinct on (code, name) code, (name || ' (' || code || ')') as name, rank from (
74
75 -- localized to user
76
77 select
78 code_country as code, l10n_country as name, 1 as rank
79 from dem.v_zip2data
80 where
81 l10n_country %(fragment_condition)s
82 %(ctxt_zip)s
83
84 union all
85
86 select
87 code as code, _(name) as name, 2 as rank
88 from dem.country
89 where
90 _(name) %(fragment_condition)s
91
92 union all
93
94 -- non-localized
95
96 select
97 code_country as code, country as name, 3 as rank
98 from dem.v_zip2data
99 where
100 country %(fragment_condition)s
101 %(ctxt_zip)s
102
103 union all
104
105 select
106 code as code, name as name, 4 as rank
107 from dem.country
108 where
109 name %(fragment_condition)s
110
111 union all
112
113 -- abbreviation
114
115 select
116 code as code, name as name, 5 as rank
117 from dem.country
118 where
119 code %(fragment_condition)s
120
121 ) as q2
122 ) as q1 order by rank, name limit 25"""
123 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context)
124 mp.setThresholds(2, 5, 9)
125 self.matcher = mp
126
127 self.unset_context(context = u'zip')
128 self.SetToolTipString(_('Type or select a country.'))
129 self.capitalisation_mode = gmTools.CAPS_FIRST
130 self.selection_only = True
131
132 #============================================================
133 # province related widgets / functions
134 #============================================================
136
137 if parent is None:
138 parent = wx.GetApp().GetTopWindow()
139
140 provs = gmDemographicRecord.get_provinces()
141
142 gmCfgWidgets.configure_string_from_list_option (
143 parent = parent,
144 message = _('Select the default region/province/state/territory for new persons.\n'),
145 option = 'person.create.default_region',
146 bias = 'user',
147 choices = [ (p['l10n_country'], p['l10n_state'], p['code_state']) for p in provs ],
148 columns = [_('Country'), _('Region'), _('Code')],
149 data = [ p['state'] for p in provs ]
150 )
151 #============================================================
153 ea = cProvinceEAPnl(parent = parent, id = -1, province = province)
154 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = (province is not None))
155 dlg.SetTitle(gmTools.coalesce(province, _('Adding province'), _('Editing province')))
156 result = dlg.ShowModal()
157 dlg.Destroy()
158 return (result == wx.ID_OK)
159 #============================================================
161
162 msg = _(
163 'Are you sure you want to delete this province ?\n'
164 '\n'
165 'Deletion will only work if this province is not\n'
166 'yet in use in any patient addresses.'
167 )
168
169 tt = _(
170 'Also delete any towns/cities/villages known\n'
171 'to be situated in this state as long as\n'
172 'no patients are recorded to live there.'
173 )
174
175 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
176 parent,
177 -1,
178 caption = _('Deleting province'),
179 question = msg,
180 show_checkbox = True,
181 checkbox_msg = _('delete related townships'),
182 checkbox_tooltip = tt,
183 button_defs = [
184 {'label': _('Yes, delete'), 'tooltip': _('Delete province and possibly related townships.'), 'default': False},
185 {'label': _('No'), 'tooltip': _('No, do NOT delete anything.'), 'default': True}
186 ]
187 )
188
189 decision = dlg.ShowModal()
190 if decision != wx.ID_YES:
191 dlg.Destroy()
192 return False
193
194 include_urbs = dlg.checkbox_is_checked()
195 dlg.Destroy()
196
197 return gmDemographicRecord.delete_province(province = province, delete_urbs = include_urbs)
198 #============================================================
200
201 if parent is None:
202 parent = wx.GetApp().GetTopWindow()
203
204 #------------------------------------------------------------
205 def delete(province=None):
206 return delete_province(parent = parent, province = province['pk_state'])
207 #------------------------------------------------------------
208 def edit(province=None):
209 return edit_province(parent = parent, province = province)
210 #------------------------------------------------------------
211 def refresh(lctrl):
212 wx.BeginBusyCursor()
213 provinces = gmDemographicRecord.get_provinces()
214 lctrl.set_string_items([ (p['l10n_country'], p['l10n_state']) for p in provinces ])
215 lctrl.set_data(provinces)
216 wx.EndBusyCursor()
217 #------------------------------------------------------------
218 msg = _(
219 '\n'
220 'This list shows the provinces known to GNUmed.\n'
221 '\n'
222 'In your jurisdiction "province" may correspond to either of "state",\n'
223 '"county", "region", "territory", or some such term.\n'
224 '\n'
225 'Select the province you want to edit !\n'
226 )
227
228 gmListWidgets.get_choices_from_list (
229 parent = parent,
230 msg = msg,
231 caption = _('Editing provinces ...'),
232 columns = [_('Country'), _('Province')],
233 single_selection = True,
234 new_callback = edit,
235 #edit_callback = edit,
236 delete_callback = delete,
237 refresh_callback = refresh
238 )
239 #============================================================
241
243
244 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
245
246 context = {
247 u'ctxt_country_name': {
248 u'where_part': u'and l10n_country ilike %(country_name)s or country ilike %(country_name)s',
249 u'placeholder': u'country_name'
250 },
251 u'ctxt_zip': {
252 u'where_part': u'and zip ilike %(zip)s',
253 u'placeholder': u'zip'
254 },
255 u'ctxt_country_code': {
256 u'where_part': u'and country in (select code from dem.country where _(name) ilike %(country_name)s or name ilike %(country_name)s)',
257 u'placeholder': u'country_name'
258 }
259 }
260
261 query = u"""
262 select code, name from (
263 select distinct on (name) code, name, rank from (
264 -- 1: find states based on name, context: zip and country name
265 select
266 code_state as code, state as name, 1 as rank
267 from dem.v_zip2data
268 where
269 state %(fragment_condition)s
270 %(ctxt_country_name)s
271 %(ctxt_zip)s
272
273 union all
274
275 -- 2: find states based on code, context: zip and country name
276 select
277 code_state as code, state as name, 2 as rank
278 from dem.v_zip2data
279 where
280 code_state %(fragment_condition)s
281 %(ctxt_country_name)s
282 %(ctxt_zip)s
283
284 union all
285
286 -- 3: find states based on name, context: country
287 select
288 code as code, name as name, 3 as rank
289 from dem.state
290 where
291 name %(fragment_condition)s
292 %(ctxt_country_code)s
293
294 union all
295
296 -- 4: find states based on code, context: country
297 select
298 code as code, name as name, 3 as rank
299 from dem.state
300 where
301 code %(fragment_condition)s
302 %(ctxt_country_code)s
303
304 ) as q2
305 ) as q1 order by rank, name limit 50"""
306
307 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context)
308 mp.setThresholds(2, 5, 6)
309 mp.word_separators = u'[ \t]+'
310 self.matcher = mp
311
312 self.unset_context(context = u'zip')
313 self.unset_context(context = u'country_name')
314 self.SetToolTipString(_('Type or select a state/region/province/territory.'))
315 self.capitalisation_mode = gmTools.CAPS_FIRST
316 self.selection_only = True
317 #====================================================================
318 from Gnumed.wxGladeWidgets import wxgProvinceEAPnl
319
321
323
324 try:
325 data = kwargs['province']
326 del kwargs['province']
327 except KeyError:
328 data = None
329
330 wxgProvinceEAPnl.wxgProvinceEAPnl.__init__(self, *args, **kwargs)
331 gmEditArea.cGenericEditAreaMixin.__init__(self)
332
333 self.mode = 'new'
334 self.data = data
335 if data is not None:
336 self.mode = 'edit'
337
338 self.__init_ui()
339 #----------------------------------------------------------------
342 #----------------------------------------------------------------
343 # generic Edit Area mixin API
344 #----------------------------------------------------------------
346
347 validity = True
348
349 if self._PRW_province.GetData() is None:
350 if self._PRW_province.GetValue().strip() == u'':
351 validity = False
352 self._PRW_province.display_as_valid(False)
353 else:
354 self._PRW_province.display_as_valid(True)
355 else:
356 self._PRW_province.display_as_valid(True)
357
358 if self._PRW_province.GetData() is None:
359 if self._TCTRL_code.GetValue().strip() == u'':
360 validity = False
361 self._TCTRL_code.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
362 else:
363 self._TCTRL_code.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
364
365 if self._PRW_country.GetData() is None:
366 validity = False
367 self._PRW_country.display_as_valid(False)
368 else:
369 self._PRW_country.display_as_valid(True)
370
371 return validity
372 #----------------------------------------------------------------
374 gmDemographicRecord.create_province (
375 name = self._PRW_province.GetValue().strip(),
376 code = self._TCTRL_code.GetValue().strip(),
377 country = self._PRW_country.GetData()
378 )
379
380 # EA is refreshed automatically after save, so need this ...
381 self.data = {
382 'l10n_state' : self._PRW_province.GetValue().strip(),
383 'code_state' : self._TCTRL_code.GetValue().strip(),
384 'l10n_country' : self._PRW_country.GetValue().strip()
385 }
386
387 return True
388 #----------------------------------------------------------------
390 # update self.data and save the changes
391 #self.data[''] =
392 #self.data[''] =
393 #self.data[''] =
394 #self.data.save()
395
396 # do nothing for now (IOW, don't support updates)
397 return True
398 #----------------------------------------------------------------
400 self._PRW_province.SetText()
401 self._TCTRL_code.SetValue(u'')
402 self._PRW_country.SetText()
403
404 self._PRW_province.SetFocus()
405 #----------------------------------------------------------------
407 self._PRW_province.SetText(self.data['l10n_state'], self.data['code_state'])
408 self._TCTRL_code.SetValue(self.data['code_state'])
409 self._PRW_country.SetText(self.data['l10n_country'], self.data['code_country'])
410
411 self._PRW_province.SetFocus()
412 #----------------------------------------------------------------
419 #============================================================
420 #============================================================
422
424
425 kwargs['message'] = _("Today's KOrganizer appointments ...")
426 kwargs['button_defs'] = [
427 {'label': _('Reload'), 'tooltip': _('Reload appointments from KOrganizer')},
428 {'label': u''},
429 {'label': u''},
430 {'label': u''},
431 {'label': u'KOrganizer', 'tooltip': _('Launch KOrganizer')}
432 ]
433 gmDataMiningWidgets.cPatientListingPnl.__init__(self, *args, **kwargs)
434
435 self.fname = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp', 'korganizer2gnumed.csv'))
436 self.reload_cmd = 'konsolekalendar --view --export-type csv --export-file %s' % self.fname
437
438 #--------------------------------------------------------
442 #--------------------------------------------------------
444 """Reload appointments from KOrganizer."""
445 found, cmd = gmShellAPI.detect_external_binary(binary = 'korganizer')
446
447 if not found:
448 gmDispatcher.send(signal = 'statustext', msg = _('KOrganizer is not installed.'), beep = True)
449 return
450
451 gmShellAPI.run_command_in_shell(command = cmd, blocking = False)
452 #--------------------------------------------------------
454 try: os.remove(self.fname)
455 except OSError: pass
456 gmShellAPI.run_command_in_shell(command=self.reload_cmd, blocking=True)
457 try:
458 csv_file = codecs.open(self.fname , mode = 'rU', encoding = 'utf8', errors = 'replace')
459 except IOError:
460 gmDispatcher.send(signal = u'statustext', msg = _('Cannot access KOrganizer transfer file [%s]') % self.fname, beep = True)
461 return
462
463 csv_lines = gmTools.unicode_csv_reader (
464 csv_file,
465 delimiter = ','
466 )
467 # start_date, start_time, end_date, end_time, title (patient), ort, comment, UID
468 self._LCTRL_items.set_columns ([
469 _('Place'),
470 _('Start'),
471 u'',
472 u'',
473 _('Patient'),
474 _('Comment')
475 ])
476 items = []
477 data = []
478 for line in csv_lines:
479 items.append([line[5], line[0], line[1], line[3], line[4], line[6]])
480 data.append([line[4], line[7]])
481
482 self._LCTRL_items.set_string_items(items = items)
483 self._LCTRL_items.set_column_widths()
484 self._LCTRL_items.set_data(data = data)
485 self._LCTRL_items.patient_key = 0
486 #--------------------------------------------------------
487 # notebook plugins API
488 #--------------------------------------------------------
490 self.reload_appointments()
491 #============================================================
493
494 pat = gmPerson.gmCurrentPatient()
495 curr_jobs = pat.get_occupations()
496 if len(curr_jobs) > 0:
497 old_job = curr_jobs[0]['l10n_occupation']
498 update = curr_jobs[0]['modified_when'].strftime('%m/%Y')
499 else:
500 old_job = u''
501 update = u''
502
503 msg = _(
504 'Please enter the primary occupation of the patient.\n'
505 '\n'
506 'Currently recorded:\n'
507 '\n'
508 ' %s (last updated %s)'
509 ) % (old_job, update)
510
511 new_job = wx.GetTextFromUser (
512 message = msg,
513 caption = _('Editing primary occupation'),
514 default_value = old_job,
515 parent = None
516 )
517 if new_job.strip() == u'':
518 return
519
520 for job in curr_jobs:
521 # unlink all but the new job
522 if job['l10n_occupation'] != new_job:
523 pat.unlink_occupation(occupation = job['l10n_occupation'])
524 # and link the new one
525 pat.link_occupation(occupation = new_job)
526 #============================================================
528 # ask user for assurance
529 go_ahead = gmGuiHelpers.gm_show_question (
530 _('Are you sure you really, positively want\n'
531 'to disable the following person ?\n'
532 '\n'
533 ' %s %s %s\n'
534 ' born %s\n'
535 '\n'
536 '%s\n'
537 ) % (
538 identity['firstnames'],
539 identity['lastnames'],
540 identity['gender'],
541 identity['dob'],
542 gmTools.bool2subst (
543 identity.is_patient,
544 _('This patient DID receive care.'),
545 _('This person did NOT receive care.')
546 )
547 ),
548 _('Disabling person')
549 )
550 if not go_ahead:
551 return True
552
553 # get admin connection
554 conn = gmAuthWidgets.get_dbowner_connection (
555 procedure = _('Disabling patient')
556 )
557 # - user cancelled
558 if conn is False:
559 return True
560 # - error
561 if conn is None:
562 return False
563
564 # now disable patient
565 gmPG2.run_rw_queries(queries = [{'cmd': u"update dem.identity set deleted=True where pk=%s", 'args': [identity['pk_identity']]}])
566
567 return True
568 #============================================================
569 # address phrasewheels and widgets
570 #============================================================
572 """A list for managing a person's addresses.
573
574 Does NOT act on/listen to the current patient.
575 """
577
578 try:
579 self.__identity = kwargs['identity']
580 del kwargs['identity']
581 except KeyError:
582 self.__identity = None
583
584 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs)
585
586 self.new_callback = self._add_address
587 self.edit_callback = self._edit_address
588 self.delete_callback = self._del_address
589 self.refresh_callback = self.refresh
590
591 self.__init_ui()
592 self.refresh()
593 #--------------------------------------------------------
594 # external API
595 #--------------------------------------------------------
597 if self.__identity is None:
598 self._LCTRL_items.set_string_items()
599 return
600
601 adrs = self.__identity.get_addresses()
602 self._LCTRL_items.set_string_items (
603 items = [ [
604 a['l10n_address_type'],
605 a['street'],
606 gmTools.coalesce(a['notes_street'], u''),
607 a['number'],
608 gmTools.coalesce(a['subunit'], u''),
609 a['postcode'],
610 a['urb'],
611 gmTools.coalesce(a['suburb'], u''),
612 a['l10n_state'],
613 a['l10n_country'],
614 gmTools.coalesce(a['notes_subunit'], u'')
615 ] for a in adrs
616 ]
617 )
618 self._LCTRL_items.set_column_widths()
619 self._LCTRL_items.set_data(data = adrs)
620 #--------------------------------------------------------
621 # internal helpers
622 #--------------------------------------------------------
624 self._LCTRL_items.SetToolTipString(_('List of known addresses.'))
625 self._LCTRL_items.set_columns(columns = [
626 _('Type'),
627 _('Street'),
628 _('Street info'),
629 _('Number'),
630 _('Subunit'),
631 _('Postal code'),
632 _('Place'),
633 _('Suburb'),
634 _('Region'),
635 _('Country'),
636 _('Comment')
637 ])
638 #--------------------------------------------------------
640 ea = cAddressEditAreaPnl(self, -1)
641 ea.identity = self.__identity
642 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea)
643 dlg.SetTitle(_('Adding new address'))
644 if dlg.ShowModal() == wx.ID_OK:
645 return True
646 return False
647 #--------------------------------------------------------
649 ea = cAddressEditAreaPnl(self, -1, address = address)
650 ea.identity = self.__identity
651 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea)
652 dlg.SetTitle(_('Editing address'))
653 if dlg.ShowModal() == wx.ID_OK:
654 # did we add an entirely new address ?
655 # if so then unlink the old one as implied by "edit"
656 if ea.address['pk_address'] != address['pk_address']:
657 self.__identity.unlink_address(address = address)
658 return True
659 return False
660 #--------------------------------------------------------
662 go_ahead = gmGuiHelpers.gm_show_question (
663 _( 'Are you sure you want to remove this\n'
664 "address from the patient's addresses ?\n"
665 '\n'
666 'The address itself will not be deleted\n'
667 'but it will no longer be associated with\n'
668 'this patient.'
669 ),
670 _('Removing address')
671 )
672 if not go_ahead:
673 return False
674 self.__identity.unlink_address(address = address)
675 return True
676 #--------------------------------------------------------
677 # properties
678 #--------------------------------------------------------
681
685
686 identity = property(_get_identity, _set_identity)
687 #============================================================
689 """A panel for editing contact data for a person.
690
691 - provides access to:
692 - addresses
693 - communication paths
694
695 Does NOT act on/listen to the current patient.
696 """
698
699 wxgPersonContactsManagerPnl.wxgPersonContactsManagerPnl.__init__(self, *args, **kwargs)
700
701 self.__identity = None
702 self.refresh()
703 #--------------------------------------------------------
704 # external API
705 #--------------------------------------------------------
709 #--------------------------------------------------------
710 # properties
711 #--------------------------------------------------------
714
718
719 identity = property(_get_identity, _set_identity)
720 #============================================================
722 """An edit area for editing/creating an address.
723
724 Does NOT act on/listen to the current patient.
725 """
727 try:
728 self.address = kwargs['address']
729 del kwargs['address']
730 except KeyError:
731 self.address = None
732
733 wxgGenericAddressEditAreaPnl.wxgGenericAddressEditAreaPnl.__init__(self, *args, **kwargs)
734
735 self.identity = None
736
737 self.__register_interests()
738 self.refresh()
739 #--------------------------------------------------------
740 # external API
741 #--------------------------------------------------------
743 if address is not None:
744 self.address = address
745
746 if self.address is not None:
747 self._PRW_type.SetText(self.address['l10n_address_type'])
748 self._PRW_zip.SetText(self.address['postcode'])
749 self._PRW_street.SetText(self.address['street'], data = self.address['street'])
750 self._TCTRL_notes_street.SetValue(gmTools.coalesce(self.address['notes_street'], ''))
751 self._TCTRL_number.SetValue(self.address['number'])
752 self._TCTRL_subunit.SetValue(gmTools.coalesce(self.address['subunit'], ''))
753 self._PRW_suburb.SetText(gmTools.coalesce(self.address['suburb'], ''))
754 self._PRW_urb.SetText(self.address['urb'], data = self.address['urb'])
755 self._PRW_state.SetText(self.address['l10n_state'], data = self.address['code_state'])
756 self._PRW_country.SetText(self.address['l10n_country'], data = self.address['code_country'])
757 self._TCTRL_notes_subunit.SetValue(gmTools.coalesce(self.address['notes_subunit'], ''))
758 # FIXME: clear fields
759 # else:
760 # pass
761 #--------------------------------------------------------
763 """Links address to patient, creating new address if necessary"""
764
765 if not self.__valid_for_save():
766 return False
767
768 # link address to patient
769 try:
770 adr = self.identity.link_address (
771 number = self._TCTRL_number.GetValue().strip(),
772 street = self._PRW_street.GetValue().strip(),
773 postcode = self._PRW_zip.GetValue().strip(),
774 urb = self._PRW_urb.GetValue().strip(),
775 state = self._PRW_state.GetData(),
776 country = self._PRW_country.GetData(),
777 subunit = gmTools.none_if(self._TCTRL_subunit.GetValue().strip(), u''),
778 suburb = gmTools.none_if(self._PRW_suburb.GetValue().strip(), u''),
779 id_type = self._PRW_type.GetData()
780 )
781 except:
782 _log.exception('cannot save address')
783 gmGuiHelpers.gm_show_error (
784 _('Cannot save address.\n\n'
785 'Does the state [%s]\n'
786 'exist in country [%s] ?'
787 ) % (
788 self._PRW_state.GetValue().strip(),
789 self._PRW_country.GetValue().strip()
790 ),
791 _('Saving address')
792 )
793 return False
794
795 notes = self._TCTRL_notes_street.GetValue().strip()
796 if notes != u'':
797 adr['notes_street'] = notes
798 notes = self._TCTRL_notes_subunit.GetValue().strip()
799 if notes != u'':
800 adr['notes_subunit'] = notes
801 adr.save_payload()
802
803 self.address = adr
804
805 return True
806 #--------------------------------------------------------
807 # event handling
808 #--------------------------------------------------------
810 self._PRW_zip.add_callback_on_lose_focus(self._on_zip_set)
811 self._PRW_country.add_callback_on_lose_focus(self._on_country_set)
812 #--------------------------------------------------------
814 """Set the street, town, state and country according to entered zip code."""
815 zip_code = self._PRW_zip.GetValue()
816 if zip_code.strip() == u'':
817 self._PRW_street.unset_context(context = u'zip')
818 self._PRW_urb.unset_context(context = u'zip')
819 self._PRW_state.unset_context(context = u'zip')
820 self._PRW_country.unset_context(context = u'zip')
821 else:
822 self._PRW_street.set_context(context = u'zip', val = zip_code)
823 self._PRW_urb.set_context(context = u'zip', val = zip_code)
824 self._PRW_state.set_context(context = u'zip', val = zip_code)
825 self._PRW_country.set_context(context = u'zip', val = zip_code)
826 #--------------------------------------------------------
828 """Set the states according to entered country."""
829 country = self._PRW_country.GetData()
830 if country is None:
831 self._PRW_state.unset_context(context = 'country')
832 else:
833 self._PRW_state.set_context(context = 'country', val = country)
834 #--------------------------------------------------------
835 # internal helpers
836 #--------------------------------------------------------
838
839 # validate required fields
840 is_any_field_filled = False
841
842 required_fields = (
843 self._PRW_type,
844 self._PRW_zip,
845 self._PRW_street,
846 self._TCTRL_number,
847 self._PRW_urb
848 )
849 for field in required_fields:
850 if len(field.GetValue().strip()) == 0:
851 if is_any_field_filled:
852 field.SetBackgroundColour('pink')
853 field.SetFocus()
854 field.Refresh()
855 gmGuiHelpers.gm_show_error (
856 _('Address details must be filled in completely or not at all.'),
857 _('Saving contact data')
858 )
859 return False
860 else:
861 is_any_field_filled = True
862 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
863 field.Refresh()
864
865 required_fields = (
866 self._PRW_state,
867 self._PRW_country
868 )
869 for field in required_fields:
870 if field.GetData() is None:
871 if is_any_field_filled:
872 field.SetBackgroundColour('pink')
873 field.SetFocus()
874 field.Refresh()
875 gmGuiHelpers.gm_show_error (
876 _('Address details must be filled in completely or not at all.'),
877 _('Saving contact data')
878 )
879 return False
880 else:
881 is_any_field_filled = True
882 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
883 field.Refresh()
884
885 return True
886 #============================================================
888
890
891 query = u"""
892 select * from (
893 (select
894 pk_address,
895 (street || ' ' || number || coalesce(' (' || subunit || ')', '') || ', '
896 || urb || coalesce(' (' || suburb || ')', '') || ', '
897 || postcode
898 || coalesce(', ' || notes_street, '')
899 || coalesce(', ' || notes_subunit, '')
900 ) as address
901 from
902 dem.v_address
903 where
904 street %(fragment_condition)s
905
906 ) union (
907
908 select
909 pk_address,
910 (street || ' ' || number || coalesce(' (' || subunit || ')', '') || ', '
911 || urb || coalesce(' (' || suburb || ')', '') || ', '
912 || postcode
913 || coalesce(', ' || notes_street, '')
914 || coalesce(', ' || notes_subunit, '')
915 ) as address
916 from
917 dem.v_address
918 where
919 postcode_street %(fragment_condition)s
920
921 ) union (
922
923 select
924 pk_address,
925 (street || ' ' || number || coalesce(' (' || subunit || ')', '') || ', '
926 || urb || coalesce(' (' || suburb || ')', '') || ', '
927 || postcode
928 || coalesce(', ' || notes_street, '')
929 || coalesce(', ' || notes_subunit, '')
930 ) as address
931 from
932 dem.v_address
933 where
934 postcode_urb %(fragment_condition)s
935 )
936 ) as union_result
937 order by union_result.address limit 50"""
938
939 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = query)
940
941 self.setThresholds(2, 4, 6)
942 # self.word_separators = u'[ \t]+'
943
944 #============================================================
946
948
949 mp = cAddressMatchProvider()
950 gmPhraseWheel.cPhraseWheel.__init__ (
951 self,
952 *args,
953 **kwargs
954 )
955 self.matcher = cAddressMatchProvider()
956 self.SetToolTipString(_('Select an address by postcode or street name.'))
957 self.selection_only = True
958 self.__address = None
959 self.__old_pk = None
960 #--------------------------------------------------------
962
963 pk = self.GetData()
964
965 if pk is None:
966 self.__address = None
967 return None
968
969 if self.__address is None:
970 self.__old_pk = pk
971 self.__address = gmDemographicRecord.cAddress(aPK_obj = pk)
972 else:
973 if pk != self.__old_pk:
974 self.__old_pk = pk
975 self.__address = gmDemographicRecord.cAddress(aPK_obj = pk)
976
977 return self.__address
978 #============================================================
980
982
983 query = u"""
984 select id, type from ((
985 select id, _(name) as type, 1 as rank
986 from dem.address_type
987 where _(name) %(fragment_condition)s
988 ) union (
989 select id, name as type, 2 as rank
990 from dem.address_type
991 where name %(fragment_condition)s
992 )) as ur
993 order by
994 ur.rank, ur.type
995 """
996 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
997 mp.setThresholds(1, 2, 4)
998 mp.word_separators = u'[ \t]+'
999 gmPhraseWheel.cPhraseWheel.__init__ (
1000 self,
1001 *args,
1002 **kwargs
1003 )
1004 self.matcher = mp
1005 self.SetToolTipString(_('Select the type of address.'))
1006 # self.capitalisation_mode = gmTools.CAPS_FIRST
1007 self.selection_only = True
1008 #--------------------------------------------------------
1009 # def GetData(self, can_create=False):
1010 # if self.data is None:
1011 # if can_create:
1012 # self.data = gmMedDoc.create_document_type(self.GetValue().strip())['pk_doc_type'] # FIXME: error handling
1013 # return self.data
1014 #============================================================
1016
1018 # FIXME: add possible context
1019 query = u"""
1020 (select distinct postcode, postcode from dem.street where postcode %(fragment_condition)s limit 20)
1021 union
1022 (select distinct postcode, postcode from dem.urb where postcode %(fragment_condition)s limit 20)"""
1023 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1024 mp.setThresholds(2, 3, 15)
1025 gmPhraseWheel.cPhraseWheel.__init__ (
1026 self,
1027 *args,
1028 **kwargs
1029 )
1030 self.SetToolTipString(_("Type or select a zip code (postcode)."))
1031 self.matcher = mp
1032 #============================================================
1034
1036 context = {
1037 u'ctxt_zip': {
1038 u'where_part': u'and zip ilike %(zip)s',
1039 u'placeholder': u'zip'
1040 }
1041 }
1042 query = u"""
1043 select s1, s2 from (
1044 select s1, s2, rank from (
1045 select distinct on (street)
1046 street as s1, street as s2, 1 as rank
1047 from dem.v_zip2data
1048 where
1049 street %(fragment_condition)s
1050 %(ctxt_zip)s
1051
1052 union all
1053
1054 select distinct on (name)
1055 name as s1, name as s2, 2 as rank
1056 from dem.street
1057 where
1058 name %(fragment_condition)s
1059
1060 ) as q2
1061 ) as q1 order by rank, s2 limit 50"""
1062 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context)
1063 mp.setThresholds(3, 5, 8)
1064 gmPhraseWheel.cPhraseWheel.__init__ (
1065 self,
1066 *args,
1067 **kwargs
1068 )
1069 self.unset_context(context = u'zip')
1070
1071 self.SetToolTipString(_('Type or select a street.'))
1072 self.capitalisation_mode = gmTools.CAPS_FIRST
1073 self.matcher = mp
1074 #============================================================
1076
1078
1079 query = """
1080 select distinct on (suburb) suburb, suburb
1081 from dem.street
1082 where suburb %(fragment_condition)s
1083 order by suburb
1084 limit 50
1085 """
1086 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1087 mp.setThresholds(2, 3, 6)
1088 gmPhraseWheel.cPhraseWheel.__init__ (
1089 self,
1090 *args,
1091 **kwargs
1092 )
1093
1094 self.SetToolTipString(_('Type or select the suburb.'))
1095 self.capitalisation_mode = gmTools.CAPS_FIRST
1096 self.matcher = mp
1097 #============================================================
1099
1101 context = {
1102 u'ctxt_zip': {
1103 u'where_part': u'and zip ilike %(zip)s',
1104 u'placeholder': u'zip'
1105 }
1106 }
1107 query = u"""
1108 select u1, u2 from (
1109 select distinct on (rank, u1)
1110 u1, u2, rank
1111 from (
1112 select
1113 urb as u1, urb as u2, 1 as rank
1114 from dem.v_zip2data
1115 where
1116 urb %(fragment_condition)s
1117 %(ctxt_zip)s
1118
1119 union all
1120
1121 select
1122 name as u1, name as u2, 2 as rank
1123 from dem.urb
1124 where
1125 name %(fragment_condition)s
1126 ) as union_result
1127 order by rank, u1
1128 ) as distincted_union
1129 limit 50
1130 """
1131 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context)
1132 mp.setThresholds(3, 5, 7)
1133 gmPhraseWheel.cPhraseWheel.__init__ (
1134 self,
1135 *args,
1136 **kwargs
1137 )
1138 self.unset_context(context = u'zip')
1139
1140 self.SetToolTipString(_('Type or select a city/town/village/dwelling.'))
1141 self.capitalisation_mode = gmTools.CAPS_FIRST
1142 self.matcher = mp
1143 #============================================================
1144 # communications channel related widgets
1145 #============================================================
1147
1149
1150 query = u"""
1151 select pk, type from ((
1152 select pk, _(description) as type, 1 as rank
1153 from dem.enum_comm_types
1154 where _(description) %(fragment_condition)s
1155 ) union (
1156 select pk, description as type, 2 as rank
1157 from dem.enum_comm_types
1158 where description %(fragment_condition)s
1159 )) as ur
1160 order by
1161 ur.rank, ur.type
1162 """
1163 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1164 mp.setThresholds(1, 2, 4)
1165 mp.word_separators = u'[ \t]+'
1166 gmPhraseWheel.cPhraseWheel.__init__ (
1167 self,
1168 *args,
1169 **kwargs
1170 )
1171 self.matcher = mp
1172 self.SetToolTipString(_('Select the type of communications channel.'))
1173 self.selection_only = True
1174 #------------------------------------------------------------
1176 """An edit area for editing/creating a comms channel.
1177
1178 Does NOT act on/listen to the current patient.
1179 """
1181 try:
1182 self.channel = kwargs['comm_channel']
1183 del kwargs['comm_channel']
1184 except KeyError:
1185 self.channel = None
1186
1187 wxgCommChannelEditAreaPnl.wxgCommChannelEditAreaPnl.__init__(self, *args, **kwargs)
1188
1189 self.identity = None
1190
1191 self.refresh()
1192 #--------------------------------------------------------
1193 # external API
1194 #--------------------------------------------------------
1196 if comm_channel is not None:
1197 self.channel = comm_channel
1198
1199 if self.channel is None:
1200 self._PRW_type.SetText(u'')
1201 self._TCTRL_url.SetValue(u'')
1202 self._PRW_address.SetText(value = u'', data = None)
1203 self._CHBOX_confidential.SetValue(False)
1204 else:
1205 self._PRW_type.SetText(self.channel['l10n_comm_type'])
1206 self._TCTRL_url.SetValue(self.channel['url'])
1207 self._PRW_address.SetData(data = self.channel['pk_address'])
1208 self._CHBOX_confidential.SetValue(self.channel['is_confidential'])
1209 #--------------------------------------------------------
1211 """Links comm channel to patient."""
1212 if self.channel is None:
1213 if not self.__valid_for_save():
1214 return False
1215 try:
1216 self.channel = self.identity.link_comm_channel (
1217 pk_channel_type = self._PRW_type.GetData(),
1218 url = self._TCTRL_url.GetValue().strip(),
1219 is_confidential = self._CHBOX_confidential.GetValue(),
1220 )
1221 except psycopg2.IntegrityError:
1222 _log.exception('error saving comm channel')
1223 gmDispatcher.send(signal = u'statustext', msg = _('Cannot save communications channel.'), beep = True)
1224 return False
1225 else:
1226 comm_type = self._PRW_type.GetValue().strip()
1227 if comm_type != u'':
1228 self.channel['comm_type'] = comm_type
1229 url = self._TCTRL_url.GetValue().strip()
1230 if url != u'':
1231 self.channel['url'] = url
1232 self.channel['is_confidential'] = self._CHBOX_confidential.GetValue()
1233
1234 self.channel['pk_address'] = self._PRW_address.GetData()
1235 self.channel.save_payload()
1236
1237 return True
1238 #--------------------------------------------------------
1239 # internal helpers
1240 #--------------------------------------------------------
1242
1243 no_errors = True
1244
1245 if self._PRW_type.GetData() is None:
1246 self._PRW_type.SetBackgroundColour('pink')
1247 self._PRW_type.SetFocus()
1248 self._PRW_type.Refresh()
1249 no_errors = False
1250 else:
1251 self._PRW_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1252 self._PRW_type.Refresh()
1253
1254 if self._TCTRL_url.GetValue().strip() == u'':
1255 self._TCTRL_url.SetBackgroundColour('pink')
1256 self._TCTRL_url.SetFocus()
1257 self._TCTRL_url.Refresh()
1258 no_errors = False
1259 else:
1260 self._TCTRL_url.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1261 self._TCTRL_url.Refresh()
1262
1263 return no_errors
1264 #------------------------------------------------------------
1266 """A list for managing a person's comm channels.
1267
1268 Does NOT act on/listen to the current patient.
1269 """
1271
1272 try:
1273 self.__identity = kwargs['identity']
1274 del kwargs['identity']
1275 except KeyError:
1276 self.__identity = None
1277
1278 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs)
1279
1280 self.new_callback = self._add_comm
1281 self.edit_callback = self._edit_comm
1282 self.delete_callback = self._del_comm
1283 self.refresh_callback = self.refresh
1284
1285 self.__init_ui()
1286 self.refresh()
1287 #--------------------------------------------------------
1288 # external API
1289 #--------------------------------------------------------
1291 if self.__identity is None:
1292 self._LCTRL_items.set_string_items()
1293 return
1294
1295 comms = self.__identity.get_comm_channels()
1296 self._LCTRL_items.set_string_items (
1297 items = [ [ gmTools.bool2str(c['is_confidential'], u'X', u''), c['l10n_comm_type'], c['url'] ] for c in comms ]
1298 )
1299 self._LCTRL_items.set_column_widths()
1300 self._LCTRL_items.set_data(data = comms)
1301 #--------------------------------------------------------
1302 # internal helpers
1303 #--------------------------------------------------------
1305 self._LCTRL_items.SetToolTipString(_('List of known communication channels.'))
1306 self._LCTRL_items.set_columns(columns = [
1307 _('confidential'),
1308 _('Type'),
1309 _('Value')
1310 ])
1311 #--------------------------------------------------------
1313 ea = cCommChannelEditAreaPnl(self, -1)
1314 ea.identity = self.__identity
1315 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea)
1316 dlg.SetTitle(_('Adding new communications channel'))
1317 if dlg.ShowModal() == wx.ID_OK:
1318 return True
1319 return False
1320 #--------------------------------------------------------
1322 ea = cCommChannelEditAreaPnl(self, -1, comm_channel = comm_channel)
1323 ea.identity = self.__identity
1324 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea)
1325 dlg.SetTitle(_('Editing communications channel'))
1326 if dlg.ShowModal() == wx.ID_OK:
1327 return True
1328 return False
1329 #--------------------------------------------------------
1331 go_ahead = gmGuiHelpers.gm_show_question (
1332 _( 'Are you sure this patient can no longer\n'
1333 "be contacted via this channel ?"
1334 ),
1335 _('Removing communication channel')
1336 )
1337 if not go_ahead:
1338 return False
1339 self.__identity.unlink_comm_channel(comm_channel = comm)
1340 return True
1341 #--------------------------------------------------------
1342 # properties
1343 #--------------------------------------------------------
1346
1350
1351 identity = property(_get_identity, _set_identity)
1352 #============================================================
1353 # identity widgets
1354 #============================================================
1355 # phrasewheels
1356 #------------------------------------------------------------
1358
1360 query = u"select distinct lastnames, lastnames from dem.names where lastnames %(fragment_condition)s order by lastnames limit 25"
1361 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1362 mp.setThresholds(3, 5, 9)
1363 gmPhraseWheel.cPhraseWheel.__init__ (
1364 self,
1365 *args,
1366 **kwargs
1367 )
1368 self.SetToolTipString(_("Type or select a last name (family name/surname)."))
1369 self.capitalisation_mode = gmTools.CAPS_NAMES
1370 self.matcher = mp
1371 #------------------------------------------------------------
1373
1375 query = u"""
1376 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
1377 union
1378 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
1379 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1380 mp.setThresholds(3, 5, 9)
1381 gmPhraseWheel.cPhraseWheel.__init__ (
1382 self,
1383 *args,
1384 **kwargs
1385 )
1386 self.SetToolTipString(_("Type or select a first name (forename/Christian name/given name)."))
1387 self.capitalisation_mode = gmTools.CAPS_NAMES
1388 self.matcher = mp
1389 #------------------------------------------------------------
1391
1393 query = u"""
1394 (select distinct preferred, preferred from dem.names where preferred %(fragment_condition)s order by preferred limit 20)
1395 union
1396 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20)
1397 union
1398 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)"""
1399 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1400 mp.setThresholds(3, 5, 9)
1401 gmPhraseWheel.cPhraseWheel.__init__ (
1402 self,
1403 *args,
1404 **kwargs
1405 )
1406 self.SetToolTipString(_("Type or select an alias (nick name, preferred name, call name, warrior name, artist name)."))
1407 # nicknames CAN start with lower case !
1408 #self.capitalisation_mode = gmTools.CAPS_NAMES
1409 self.matcher = mp
1410 #------------------------------------------------------------
1412
1414 query = u"select distinct title, title from dem.identity where title %(fragment_condition)s"
1415 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1416 mp.setThresholds(1, 3, 9)
1417 gmPhraseWheel.cPhraseWheel.__init__ (
1418 self,
1419 *args,
1420 **kwargs
1421 )
1422 self.SetToolTipString(_("Type or select a title. Note that the title applies to the person, not to a particular name !"))
1423 self.matcher = mp
1424 #------------------------------------------------------------
1426 """Let user select a gender."""
1427
1428 _gender_map = None
1429
1431
1432 if cGenderSelectionPhraseWheel._gender_map is None:
1433 cmd = u"""
1434 select tag, l10n_label, sort_weight
1435 from dem.v_gender_labels
1436 order by sort_weight desc"""
1437 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True)
1438 cGenderSelectionPhraseWheel._gender_map = {}
1439 for gender in rows:
1440 cGenderSelectionPhraseWheel._gender_map[gender[idx['tag']]] = {
1441 'data': gender[idx['tag']],
1442 'label': gender[idx['l10n_label']],
1443 'weight': gender[idx['sort_weight']]
1444 }
1445
1446 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = cGenderSelectionPhraseWheel._gender_map.values())
1447 mp.setThresholds(1, 1, 3)
1448
1449 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1450 self.selection_only = True
1451 self.matcher = mp
1452 self.picklist_delay = 50
1453 #------------------------------------------------------------
1455
1457 query = u"select distinct name, _(name) from dem.occupation where _(name) %(fragment_condition)s"
1458 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1459 mp.setThresholds(1, 3, 5)
1460 gmPhraseWheel.cPhraseWheel.__init__ (
1461 self,
1462 *args,
1463 **kwargs
1464 )
1465 self.SetToolTipString(_("Type or select an occupation."))
1466 self.capitalisation_mode = gmTools.CAPS_FIRST
1467 self.matcher = mp
1468 #------------------------------------------------------------
1470
1472 query = u"""
1473 select distinct pk, (name || coalesce(' (%s ' || issuer || ')', '')) as label
1474 from dem.enum_ext_id_types
1475 where name %%(fragment_condition)s
1476 order by label limit 25""" % _('issued by')
1477 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1478 mp.setThresholds(1, 3, 5)
1479 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1480 self.SetToolTipString(_("Enter or select a type for the external ID."))
1481 self.matcher = mp
1482 #------------------------------------------------------------
1484
1486 query = u"""
1487 select distinct issuer, issuer
1488 from dem.enum_ext_id_types
1489 where issuer %(fragment_condition)s
1490 order by issuer limit 25"""
1491 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1492 mp.setThresholds(1, 3, 5)
1493 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1494 self.SetToolTipString(_("Type or select an occupation."))
1495 self.capitalisation_mode = gmTools.CAPS_FIRST
1496 self.matcher = mp
1497 #------------------------------------------------------------
1498 # edit areas
1499 #------------------------------------------------------------
1501 """An edit area for editing/creating external IDs.
1502
1503 Does NOT act on/listen to the current patient.
1504 """
1506
1507 try:
1508 self.ext_id = kwargs['external_id']
1509 del kwargs['external_id']
1510 except:
1511 self.ext_id = None
1512
1513 wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl.__init__(self, *args, **kwargs)
1514
1515 self.identity = None
1516
1517 self.__register_events()
1518
1519 self.refresh()
1520 #--------------------------------------------------------
1521 # external API
1522 #--------------------------------------------------------
1524 if ext_id is not None:
1525 self.ext_id = ext_id
1526
1527 if self.ext_id is not None:
1528 self._PRW_type.SetText(value = self.ext_id['name'], data = self.ext_id['pk_type'])
1529 self._TCTRL_value.SetValue(self.ext_id['value'])
1530 self._PRW_issuer.SetText(self.ext_id['issuer'])
1531 self._TCTRL_comment.SetValue(gmTools.coalesce(self.ext_id['comment'], u''))
1532 # FIXME: clear fields
1533 # else:
1534 # pass
1535 #--------------------------------------------------------
1537
1538 if not self.__valid_for_save():
1539 return False
1540
1541 # strip out " (issued by ...)" added by phrasewheel
1542 type = regex.split(' \(%s .+\)$' % _('issued by'), self._PRW_type.GetValue().strip(), 1)[0]
1543
1544 # add new external ID
1545 if self.ext_id is None:
1546 self.identity.add_external_id (
1547 type_name = type,
1548 value = self._TCTRL_value.GetValue().strip(),
1549 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''),
1550 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1551 )
1552 # edit old external ID
1553 else:
1554 self.identity.update_external_id (
1555 pk_id = self.ext_id['pk_id'],
1556 type = type,
1557 value = self._TCTRL_value.GetValue().strip(),
1558 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''),
1559 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1560 )
1561
1562 return True
1563 #--------------------------------------------------------
1564 # internal helpers
1565 #--------------------------------------------------------
1567 self._PRW_type.add_callback_on_lose_focus(self._on_type_set)
1568 #--------------------------------------------------------
1570 """Set the issuer according to the selected type.
1571
1572 Matches are fetched from existing records in backend.
1573 """
1574 pk_curr_type = self._PRW_type.GetData()
1575 if pk_curr_type is None:
1576 return True
1577 rows, idx = gmPG2.run_ro_queries(queries = [{
1578 'cmd': u"select issuer from dem.enum_ext_id_types where pk = %s",
1579 'args': [pk_curr_type]
1580 }])
1581 if len(rows) == 0:
1582 return True
1583 wx.CallAfter(self._PRW_issuer.SetText, rows[0][0])
1584 return True
1585 #--------------------------------------------------------
1587
1588 no_errors = True
1589
1590 # do not test .GetData() because adding external IDs
1591 # will create types if necessary
1592 # if self._PRW_type.GetData() is None:
1593 if self._PRW_type.GetValue().strip() == u'':
1594 self._PRW_type.SetBackgroundColour('pink')
1595 self._PRW_type.SetFocus()
1596 self._PRW_type.Refresh()
1597 else:
1598 self._PRW_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1599 self._PRW_type.Refresh()
1600
1601 if self._TCTRL_value.GetValue().strip() == u'':
1602 self._TCTRL_value.SetBackgroundColour('pink')
1603 self._TCTRL_value.SetFocus()
1604 self._TCTRL_value.Refresh()
1605 no_errors = False
1606 else:
1607 self._TCTRL_value.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1608 self._TCTRL_value.Refresh()
1609
1610 return no_errors
1611 #------------------------------------------------------------
1613 """An edit area for editing/creating name/gender/dob.
1614
1615 Does NOT act on/listen to the current patient.
1616 """
1618
1619 self.__name = kwargs['name']
1620 del kwargs['name']
1621 self.__identity = gmPerson.cIdentity(aPK_obj = self.__name['pk_identity'])
1622
1623 wxgNameGenderDOBEditAreaPnl.wxgNameGenderDOBEditAreaPnl.__init__(self, *args, **kwargs)
1624
1625 self.__register_interests()
1626 self.refresh()
1627 #--------------------------------------------------------
1628 # external API
1629 #--------------------------------------------------------
1631 if self.__name is None:
1632 return
1633
1634 self._PRW_title.SetText(gmTools.coalesce(self.__name['title'], u''))
1635 self._PRW_firstname.SetText(self.__name['firstnames'])
1636 self._PRW_lastname.SetText(self.__name['lastnames'])
1637 self._PRW_nick.SetText(gmTools.coalesce(self.__name['preferred'], u''))
1638 dob = self.__identity['dob']
1639 self._PRW_dob.SetText(value = dob.strftime('%Y-%m-%d %H:%M'), data = dob)
1640 self._PRW_gender.SetData(self.__name['gender'])
1641 self._CHBOX_active.SetValue(self.__name['active_name'])
1642 self._TCTRL_comment.SetValue(gmTools.coalesce(self.__name['comment'], u''))
1643 # FIXME: clear fields
1644 # else:
1645 # pass
1646 #--------------------------------------------------------
1648
1649 if not self.__valid_for_save():
1650 return False
1651
1652 self.__identity['gender'] = self._PRW_gender.GetData()
1653 if self._PRW_dob.GetValue().strip() == u'':
1654 self.__identity['dob'] = None
1655 else:
1656 self.__identity['dob'] = self._PRW_dob.GetData().get_pydt()
1657 self.__identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'')
1658 self.__identity.save_payload()
1659
1660 active = self._CHBOX_active.GetValue()
1661 first = self._PRW_firstname.GetValue().strip()
1662 last = self._PRW_lastname.GetValue().strip()
1663 old_nick = self.__name['preferred']
1664
1665 # is it a new name ?
1666 old_name = self.__name['firstnames'] + self.__name['lastnames']
1667 if (first + last) != old_name:
1668 self.__name = self.__identity.add_name(first, last, active)
1669
1670 self.__name['active_name'] = active
1671 self.__name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'')
1672 self.__name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
1673
1674 self.__name.save_payload()
1675
1676 return True
1677 #--------------------------------------------------------
1678 # event handling
1679 #--------------------------------------------------------
1681 self._PRW_firstname.add_callback_on_lose_focus(self._on_name_set)
1682 #--------------------------------------------------------
1684 """Set the gender according to entered firstname.
1685
1686 Matches are fetched from existing records in backend.
1687 """
1688 firstname = self._PRW_firstname.GetValue().strip()
1689 if firstname == u'':
1690 return True
1691 rows, idx = gmPG2.run_ro_queries(queries = [{
1692 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
1693 'args': [firstname]
1694 }])
1695 if len(rows) == 0:
1696 return True
1697 wx.CallAfter(self._PRW_gender.SetData, rows[0][0])
1698 return True
1699 #--------------------------------------------------------
1700 # internal helpers
1701 #--------------------------------------------------------
1703
1704 error_found = True
1705
1706 if self._PRW_gender.GetData() is None:
1707 self._PRW_gender.SetBackgroundColour('pink')
1708 self._PRW_gender.Refresh()
1709 self._PRW_gender.SetFocus()
1710 error_found = False
1711 else:
1712 self._PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1713 self._PRW_gender.Refresh()
1714
1715 if not self._PRW_dob.is_valid_timestamp():
1716 val = self._PRW_dob.GetValue().strip()
1717 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val)
1718 self._PRW_dob.SetBackgroundColour('pink')
1719 self._PRW_dob.Refresh()
1720 self._PRW_dob.SetFocus()
1721 error_found = False
1722 else:
1723 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1724 self._PRW_dob.Refresh()
1725
1726 if self._PRW_lastname.GetValue().strip() == u'':
1727 self._PRW_lastname.SetBackgroundColour('pink')
1728 self._PRW_lastname.Refresh()
1729 self._PRW_lastname.SetFocus()
1730 error_found = False
1731 else:
1732 self._PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1733 self._PRW_lastname.Refresh()
1734
1735 if self._PRW_firstname.GetValue().strip() == u'':
1736 self._PRW_firstname.SetBackgroundColour('pink')
1737 self._PRW_firstname.Refresh()
1738 self._PRW_firstname.SetFocus()
1739 error_found = False
1740 else:
1741 self._PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1742 self._PRW_firstname.Refresh()
1743
1744 return error_found
1745 #------------------------------------------------------------
1746 # list manager
1747 #------------------------------------------------------------
1749 """A list for managing a person's names.
1750
1751 Does NOT act on/listen to the current patient.
1752 """
1754
1755 try:
1756 self.__identity = kwargs['identity']
1757 del kwargs['identity']
1758 except KeyError:
1759 self.__identity = None
1760
1761 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs)
1762
1763 self.new_callback = self._add_name
1764 self.edit_callback = self._edit_name
1765 self.delete_callback = self._del_name
1766 self.refresh_callback = self.refresh
1767
1768 self.__init_ui()
1769 self.refresh()
1770 #--------------------------------------------------------
1771 # external API
1772 #--------------------------------------------------------
1774 if self.__identity is None:
1775 self._LCTRL_items.set_string_items()
1776 return
1777
1778 names = self.__identity.get_names()
1779 self._LCTRL_items.set_string_items (
1780 items = [ [
1781 gmTools.bool2str(n['active_name'], 'X', ''),
1782 gmTools.coalesce(n['title'], gmPerson.map_gender2salutation(n['gender'])),
1783 n['lastnames'],
1784 n['firstnames'],
1785 gmTools.coalesce(n['preferred'], u''),
1786 gmTools.coalesce(n['comment'], u'')
1787 ] for n in names ]
1788 )
1789 self._LCTRL_items.set_column_widths()
1790 self._LCTRL_items.set_data(data = names)
1791 #--------------------------------------------------------
1792 # internal helpers
1793 #--------------------------------------------------------
1795 self._LCTRL_items.set_columns(columns = [
1796 _('Active'),
1797 _('Title'),
1798 _('Lastname'),
1799 _('Firstname(s)'),
1800 _('Preferred Name'),
1801 _('Comment')
1802 ])
1803 #--------------------------------------------------------
1805 ea = cNameGenderDOBEditAreaPnl(self, -1, name = self.__identity.get_active_name())
1806 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea)
1807 dlg.SetTitle(_('Adding new name'))
1808 if dlg.ShowModal() == wx.ID_OK:
1809 dlg.Destroy()
1810 return True
1811 dlg.Destroy()
1812 return False
1813 #--------------------------------------------------------
1815 ea = cNameGenderDOBEditAreaPnl(self, -1, name = name)
1816 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea)
1817 dlg.SetTitle(_('Editing name'))
1818 if dlg.ShowModal() == wx.ID_OK:
1819 dlg.Destroy()
1820 return True
1821 dlg.Destroy()
1822 return False
1823 #--------------------------------------------------------
1825
1826 if len(self.__identity.get_names()) == 1:
1827 gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the only name of a person.'), beep = True)
1828 return False
1829
1830 go_ahead = gmGuiHelpers.gm_show_question (
1831 _( 'It is often advisable to keep old names around and\n'
1832 'just create a new "currently active" name.\n'
1833 '\n'
1834 'This allows finding the patient by both the old\n'
1835 'and the new name (think before/after marriage).\n'
1836 '\n'
1837 'Do you still want to really delete\n'
1838 "this name from the patient ?"
1839 ),
1840 _('Deleting name')
1841 )
1842 if not go_ahead:
1843 return False
1844
1845 self.__identity.delete_name(name = name)
1846 return True
1847 #--------------------------------------------------------
1848 # properties
1849 #--------------------------------------------------------
1852
1856
1857 identity = property(_get_identity, _set_identity)
1858 #------------------------------------------------------------
1860 """A list for managing a person's external IDs.
1861
1862 Does NOT act on/listen to the current patient.
1863 """
1865
1866 try:
1867 self.__identity = kwargs['identity']
1868 del kwargs['identity']
1869 except KeyError:
1870 self.__identity = None
1871
1872 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs)
1873
1874 self.new_callback = self._add_id
1875 self.edit_callback = self._edit_id
1876 self.delete_callback = self._del_id
1877 self.refresh_callback = self.refresh
1878
1879 self.__init_ui()
1880 self.refresh()
1881 #--------------------------------------------------------
1882 # external API
1883 #--------------------------------------------------------
1885 if self.__identity is None:
1886 self._LCTRL_items.set_string_items()
1887 return
1888
1889 ids = self.__identity.get_external_ids()
1890 self._LCTRL_items.set_string_items (
1891 items = [ [
1892 i['name'],
1893 i['value'],
1894 gmTools.coalesce(i['issuer'], u''),
1895 i['context'],
1896 gmTools.coalesce(i['comment'], u'')
1897 ] for i in ids
1898 ]
1899 )
1900 self._LCTRL_items.set_column_widths()
1901 self._LCTRL_items.set_data(data = ids)
1902 #--------------------------------------------------------
1903 # internal helpers
1904 #--------------------------------------------------------
1906 self._LCTRL_items.set_columns(columns = [
1907 _('ID type'),
1908 _('Value'),
1909 _('Issuer'),
1910 _('Context'),
1911 _('Comment')
1912 ])
1913 #--------------------------------------------------------
1915 ea = cExternalIDEditAreaPnl(self, -1)
1916 ea.identity = self.__identity
1917 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea)
1918 dlg.SetTitle(_('Adding new external ID'))
1919 if dlg.ShowModal() == wx.ID_OK:
1920 dlg.Destroy()
1921 return True
1922 dlg.Destroy()
1923 return False
1924 #--------------------------------------------------------
1926 ea = cExternalIDEditAreaPnl(self, -1, external_id = ext_id)
1927 ea.identity = self.__identity
1928 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea)
1929 dlg.SetTitle(_('Editing external ID'))
1930 if dlg.ShowModal() == wx.ID_OK:
1931 dlg.Destroy()
1932 return True
1933 dlg.Destroy()
1934 return False
1935 #--------------------------------------------------------
1937 go_ahead = gmGuiHelpers.gm_show_question (
1938 _( 'Do you really want to delete this\n'
1939 'external ID from the patient ?'),
1940 _('Deleting external ID')
1941 )
1942 if not go_ahead:
1943 return False
1944 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id'])
1945 return True
1946 #--------------------------------------------------------
1947 # properties
1948 #--------------------------------------------------------
1951
1955
1956 identity = property(_get_identity, _set_identity)
1957 #------------------------------------------------------------
1958 # integrated panels
1959 #------------------------------------------------------------
1961 """A panel for editing identity data for a person.
1962
1963 - provides access to:
1964 - name
1965 - external IDs
1966
1967 Does NOT act on/listen to the current patient.
1968 """
1970
1971 wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl.__init__(self, *args, **kwargs)
1972
1973 self.__identity = None
1974 self.refresh()
1975 #--------------------------------------------------------
1976 # external API
1977 #--------------------------------------------------------
1981 #--------------------------------------------------------
1982 # properties
1983 #--------------------------------------------------------
1986
1990
1991 identity = property(_get_identity, _set_identity)
1992 #============================================================
1993 # new-patient widgets
1994 #============================================================
1996
1997 dbcfg = gmCfg.cCfgSQL()
1998
1999 def_region = dbcfg.get2 (
2000 option = u'person.create.default_region',
2001 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2002 bias = u'user'
2003 )
2004
2005 if def_region is None:
2006 def_country = dbcfg.get2 (
2007 option = u'person.create.default_country',
2008 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2009 bias = u'user'
2010 )
2011 else:
2012 countries = gmDemographicRecord.get_country_for_region(region = def_region)
2013 if len(countries) == 1:
2014 def_country = countries[0]['l10n_country']
2015
2016 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region)
2017 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
2018 dlg.SetTitle(_('Adding new person'))
2019 ea._PRW_lastname.SetFocus()
2020 result = dlg.ShowModal()
2021 pat = ea.data
2022 dlg.Destroy()
2023
2024 if result != wx.ID_OK:
2025 return False
2026
2027 if activate:
2028 from Gnumed.wxpython import gmPatSearchWidgets
2029 gmPatSearchWidgets.set_active_patient(patient = pat)
2030
2031 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin')
2032
2033 return True
2034 #============================================================
2035 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl
2036
2037 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
2038
2040
2041 try:
2042 self.default_region = kwargs['region']
2043 del kwargs['region']
2044 except KeyError:
2045 self.default_region = None
2046
2047 try:
2048 self.default_country = kwargs['country']
2049 del kwargs['country']
2050 except KeyError:
2051 self.default_country = None
2052
2053 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs)
2054 gmEditArea.cGenericEditAreaMixin.__init__(self)
2055
2056 self.mode = 'new'
2057 self.data = None
2058 self._address = None
2059
2060 self.__init_ui()
2061 self.__register_interests()
2062 #----------------------------------------------------------------
2063 # internal helpers
2064 #----------------------------------------------------------------
2066 self._PRW_lastname.final_regex = '.+'
2067 self._PRW_firstnames.final_regex = '.+'
2068 self._PRW_address_searcher.selection_only = False
2069 low = wx.DateTimeFromDMY(1,0,1900)
2070 hi = wx.DateTime()
2071 self._DP_dob.SetRange(low, hi.SetToCurrent())
2072 # only if we would support None on selection_only's
2073 #self._PRW_external_id_type.selection_only = True
2074
2075 if self.default_country is not None:
2076 self._PRW_country.SetText(value = self.default_country)
2077
2078 if self.default_region is not None:
2079 self._PRW_region.SetText(value = self.default_region)
2080 #----------------------------------------------------------------
2082
2083 adr = self._PRW_address_searcher.get_address()
2084 if adr is None:
2085 return True
2086
2087 if ctrl.GetValue().strip() != adr[field]:
2088 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None)
2089 return True
2090
2091 return False
2092 #----------------------------------------------------------------
2094 adr = self._PRW_address_searcher.get_address()
2095 if adr is None:
2096 return True
2097
2098 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode'])
2099
2100 self._PRW_street.SetText(value = adr['street'], data = adr['street'])
2101 self._PRW_street.set_context(context = u'zip', val = adr['postcode'])
2102
2103 self._TCTRL_number.SetValue(adr['number'])
2104
2105 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb'])
2106 self._PRW_urb.set_context(context = u'zip', val = adr['postcode'])
2107
2108 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state'])
2109 self._PRW_region.set_context(context = u'zip', val = adr['postcode'])
2110
2111 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country'])
2112 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
2113 #----------------------------------------------------------------
2115 error = False
2116
2117 # name fields
2118 if self._PRW_lastname.GetValue().strip() == u'':
2119 error = True
2120 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
2121 self._PRW_lastname.display_as_valid(False)
2122 else:
2123 self._PRW_lastname.display_as_valid(True)
2124
2125 if self._PRW_firstnames.GetValue().strip() == '':
2126 error = True
2127 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
2128 self._PRW_firstnames.display_as_valid(False)
2129 else:
2130 self._PRW_firstnames.display_as_valid(True)
2131
2132 # gender
2133 if self._PRW_gender.GetData() is None:
2134 error = True
2135 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
2136 self._PRW_gender.display_as_valid(False)
2137 else:
2138 self._PRW_gender.display_as_valid(True)
2139
2140 # dob validation
2141 if not self._DP_dob.is_valid_timestamp():
2142
2143 gmDispatcher.send(signal = 'statustext', msg = _('Cannot use this date of birth. Does it lie before 1900 ?'))
2144
2145 do_it_anyway = gmGuiHelpers.gm_show_question (
2146 _(
2147 'Are you sure you want to register this person\n'
2148 'without a valid date of birth ?\n'
2149 '\n'
2150 'This can be useful for temporary staff members\n'
2151 'but will provoke nag screens if this person\n'
2152 'becomes a patient.\n'
2153 '\n'
2154 'Note that the date of birth cannot technically\n'
2155 'be before 1900, either :-(\n'
2156 ),
2157 _('Registering new person')
2158 )
2159
2160 if not do_it_anyway:
2161 error = True
2162
2163 if self._DP_dob.GetValue().GetYear() < 1900:
2164 error = True
2165 gmDispatcher.send(signal = 'statustext', msg = _('The year of birth must lie after 1900.'), beep = True)
2166 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
2167 self._DP_dob.SetFocus()
2168 else:
2169 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2170 self._DP_dob.Refresh()
2171
2172 # TOB validation if non-empty
2173 # if self._TCTRL_tob.GetValue().strip() != u'':
2174
2175 return (not error)
2176 #----------------------------------------------------------------
2178
2179 # existing address ? if so set other fields
2180 if self._PRW_address_searcher.GetData() is not None:
2181 wx.CallAfter(self.__set_fields_from_address_searcher)
2182 return True
2183
2184 # must either all contain something or none of them
2185 fields_to_fill = (
2186 self._TCTRL_number,
2187 self._PRW_zip,
2188 self._PRW_street,
2189 self._PRW_urb,
2190 self._PRW_region,
2191 self._PRW_country
2192 )
2193 no_of_filled_fields = 0
2194
2195 for field in fields_to_fill:
2196 if field.GetValue().strip() != u'':
2197 no_of_filled_fields += 1
2198 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2199 field.Refresh()
2200
2201 # empty address ?
2202 if no_of_filled_fields == 0:
2203 if empty_address_is_valid:
2204 return True
2205 else:
2206 return None
2207
2208 # incompletely filled address ?
2209 if no_of_filled_fields != len(fields_to_fill):
2210 for field in fields_to_fill:
2211 if field.GetValue().strip() == u'':
2212 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
2213 field.SetFocus()
2214 field.Refresh()
2215 msg = _('To properly create an address, all the related fields must be filled in.')
2216 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2217 return False
2218
2219 # fields which must contain a selected item
2220 # FIXME: they must also contain an *acceptable combination* which
2221 # FIXME: can only be tested against the database itself ...
2222 strict_fields = (
2223 self._PRW_region,
2224 self._PRW_country
2225 )
2226 error = False
2227 for field in strict_fields:
2228 if field.GetData() is None:
2229 error = True
2230 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
2231 field.SetFocus()
2232 else:
2233 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
2234 field.Refresh()
2235
2236 if error:
2237 msg = _('This field must contain an item selected from the dropdown list.')
2238 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2239 return False
2240
2241 return True
2242 #----------------------------------------------------------------
2244
2245 # identity
2246 self._PRW_firstnames.add_callback_on_lose_focus(self._on_leaving_firstname)
2247
2248 # address
2249 self._PRW_address_searcher.add_callback_on_lose_focus(self._on_leaving_adress_searcher)
2250
2251 # invalidate address searcher when any field edited
2252 self._PRW_street.add_callback_on_lose_focus(self._invalidate_address_searcher)
2253 wx.EVT_KILL_FOCUS(self._TCTRL_number, self._invalidate_address_searcher)
2254 self._PRW_urb.add_callback_on_lose_focus(self._invalidate_address_searcher)
2255 self._PRW_region.add_callback_on_lose_focus(self._invalidate_address_searcher)
2256
2257 self._PRW_zip.add_callback_on_lose_focus(self._on_leaving_zip)
2258 self._PRW_country.add_callback_on_lose_focus(self._on_leaving_country)
2259 #----------------------------------------------------------------
2260 # event handlers
2261 #----------------------------------------------------------------
2263 """Set the gender according to entered firstname.
2264
2265 Matches are fetched from existing records in backend.
2266 """
2267 # only set if not already set so as to not
2268 # overwrite a change by the user
2269 if self._PRW_gender.GetData() is not None:
2270 return True
2271
2272 firstname = self._PRW_firstnames.GetValue().strip()
2273 if firstname == u'':
2274 return True
2275
2276 gender = gmPerson.map_firstnames2gender(firstnames = firstname)
2277 if gender is None:
2278 return True
2279
2280 wx.CallAfter(self._PRW_gender.SetData, gender)
2281 return True
2282 #----------------------------------------------------------------
2284 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode')
2285
2286 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'')
2287 self._PRW_street.set_context(context = u'zip', val = zip_code)
2288 self._PRW_urb.set_context(context = u'zip', val = zip_code)
2289 self._PRW_region.set_context(context = u'zip', val = zip_code)
2290 self._PRW_country.set_context(context = u'zip', val = zip_code)
2291
2292 return True
2293 #----------------------------------------------------------------
2295 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country')
2296
2297 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'')
2298 self._PRW_region.set_context(context = u'country', val = country)
2299
2300 return True
2301 #----------------------------------------------------------------
2303 mapping = [
2304 (self._PRW_street, 'street'),
2305 (self._TCTRL_number, 'number'),
2306 (self._PRW_urb, 'urb'),
2307 (self._PRW_region, 'l10n_state')
2308 ]
2309
2310 # loop through fields and invalidate address searcher if different
2311 for ctrl, field in mapping:
2312 if self.__perhaps_invalidate_address_searcher(ctrl, field):
2313 return True
2314
2315 return True
2316 #----------------------------------------------------------------
2318 adr = self._PRW_address_searcher.get_address()
2319 if adr is None:
2320 return True
2321
2322 wx.CallAfter(self.__set_fields_from_address_searcher)
2323 return True
2324 #----------------------------------------------------------------
2325 # generic Edit Area mixin API
2326 #----------------------------------------------------------------
2328 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
2329 #----------------------------------------------------------------
2331
2332 # identity
2333 new_identity = gmPerson.create_identity (
2334 gender = self._PRW_gender.GetData(),
2335 dob = self._DP_dob.get_pydt(),
2336 lastnames = self._PRW_lastname.GetValue().strip(),
2337 firstnames = self._PRW_firstnames.GetValue().strip()
2338 )
2339 _log.debug('identity created: %s' % new_identity)
2340
2341 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip())
2342 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u''))
2343 #TOB
2344 new_identity.save()
2345
2346 name = new_identity.get_active_name()
2347 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
2348 name.save()
2349
2350 # address
2351 is_valid = self.__address_valid_for_save(empty_address_is_valid = False)
2352 if is_valid is True:
2353 # because we currently only check for non-emptiness
2354 # we must still deal with database errors
2355 try:
2356 new_identity.link_address (
2357 number = self._TCTRL_number.GetValue().strip(),
2358 street = self._PRW_street.GetValue().strip(),
2359 postcode = self._PRW_zip.GetValue().strip(),
2360 urb = self._PRW_urb.GetValue().strip(),
2361 state = self._PRW_region.GetData(),
2362 country = self._PRW_country.GetData()
2363 )
2364 except psycopg2.InternalError:
2365 #except StandardError:
2366 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip())
2367 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip())
2368 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip())
2369 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip())
2370 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip())
2371 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip())
2372 _log.exception('cannot link address')
2373 gmGuiHelpers.gm_show_error (
2374 aTitle = _('Saving address'),
2375 aMessage = _(
2376 'Cannot save this address.\n'
2377 '\n'
2378 'You will have to add it via the Demographics plugin.\n'
2379 )
2380 )
2381 elif is_valid is False:
2382 gmGuiHelpers.gm_show_error (
2383 aTitle = _('Saving address'),
2384 aMessage = _(
2385 'Address not saved.\n'
2386 '\n'
2387 'You will have to add it via the Demographics plugin.\n'
2388 )
2389 )
2390 # else it is None which means empty address which we ignore
2391
2392 # phone
2393 new_identity.link_comm_channel (
2394 comm_medium = u'homephone',
2395 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''),
2396 is_confidential = False
2397 )
2398
2399 # external ID
2400 pk_type = self._PRW_external_id_type.GetData()
2401 id_value = self._TCTRL_external_id_value.GetValue().strip()
2402 if (pk_type is not None) and (id_value != u''):
2403 new_identity.add_external_id(value = id_value, pk_type = pk_type)
2404
2405 # occupation
2406 new_identity.link_occupation (
2407 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'')
2408 )
2409
2410 self.data = new_identity
2411 return True
2412 #----------------------------------------------------------------
2415 #----------------------------------------------------------------
2419 #----------------------------------------------------------------
2422 #----------------------------------------------------------------
2425 #============================================================
2426 # new-patient wizard classes
2427 #============================================================
2429 """
2430 Wizard page for entering patient's basic demographic information
2431 """
2432
2433 form_fields = (
2434 'firstnames', 'lastnames', 'nick', 'dob', 'gender', 'title', 'occupation',
2435 'address_number', 'zip_code', 'street', 'town', 'state', 'country', 'phone', 'comment'
2436 )
2437
2439 """
2440 Creates a new instance of BasicPatDetailsPage
2441 @param parent - The parent widget
2442 @type parent - A wx.Window instance
2443 @param tile - The title of the page
2444 @type title - A StringType instance
2445 """
2446 wx.wizard.WizardPageSimple.__init__(self, parent) #, bitmap = gmGuiHelpers.gm_icon(_('oneperson'))
2447 self.__title = title
2448 self.__do_layout()
2449 self.__register_interests()
2450 #--------------------------------------------------------
2452 PNL_form = wx.Panel(self, -1)
2453
2454 # last name
2455 STT_lastname = wx.StaticText(PNL_form, -1, _('Last name'))
2456 STT_lastname.SetForegroundColour('red')
2457 self.PRW_lastname = cLastnamePhraseWheel(parent = PNL_form, id = -1)
2458 self.PRW_lastname.SetToolTipString(_('Required: lastname (family name)'))
2459
2460 # first name
2461 STT_firstname = wx.StaticText(PNL_form, -1, _('First name(s)'))
2462 STT_firstname.SetForegroundColour('red')
2463 self.PRW_firstname = cFirstnamePhraseWheel(parent = PNL_form, id = -1)
2464 self.PRW_firstname.SetToolTipString(_('Required: surname/given name/first name'))
2465
2466 # nickname
2467 STT_nick = wx.StaticText(PNL_form, -1, _('Nick name'))
2468 self.PRW_nick = cNicknamePhraseWheel(parent = PNL_form, id = -1)
2469
2470 # DOB
2471 STT_dob = wx.StaticText(PNL_form, -1, _('Date of birth'))
2472 STT_dob.SetForegroundColour('red')
2473 self.PRW_dob = gmDateTimeInput.cFuzzyTimestampInput(parent = PNL_form, id = -1)
2474 self.PRW_dob.SetToolTipString(_("Required: date of birth, if unknown or aliasing wanted then invent one"))
2475
2476 # gender
2477 STT_gender = wx.StaticText(PNL_form, -1, _('Gender'))
2478 STT_gender.SetForegroundColour('red')
2479 self.PRW_gender = cGenderSelectionPhraseWheel(parent = PNL_form, id=-1)
2480 self.PRW_gender.SetToolTipString(_("Required: gender of patient"))
2481
2482 # title
2483 STT_title = wx.StaticText(PNL_form, -1, _('Title'))
2484 self.PRW_title = cTitlePhraseWheel(parent = PNL_form, id = -1)
2485
2486 # zip code
2487 STT_zip_code = wx.StaticText(PNL_form, -1, _('Postal code'))
2488 STT_zip_code.SetForegroundColour('orange')
2489 self.PRW_zip_code = cZipcodePhraseWheel(parent = PNL_form, id = -1)
2490 self.PRW_zip_code.SetToolTipString(_("primary/home address: zip/postal code"))
2491
2492 # street
2493 STT_street = wx.StaticText(PNL_form, -1, _('Street'))
2494 STT_street.SetForegroundColour('orange')
2495 self.PRW_street = cStreetPhraseWheel(parent = PNL_form, id = -1)
2496 self.PRW_street.SetToolTipString(_("primary/home address: name of street"))
2497
2498 # address number
2499 STT_address_number = wx.StaticText(PNL_form, -1, _('Number'))
2500 STT_address_number.SetForegroundColour('orange')
2501 self.TTC_address_number = wx.TextCtrl(PNL_form, -1)
2502 self.TTC_address_number.SetToolTipString(_("primary/home address: address number"))
2503
2504 # town
2505 STT_town = wx.StaticText(PNL_form, -1, _('Place'))
2506 STT_town.SetForegroundColour('orange')
2507 self.PRW_town = cUrbPhraseWheel(parent = PNL_form, id = -1)
2508 self.PRW_town.SetToolTipString(_("primary/home address: city/town/village/dwelling/..."))
2509
2510 # state
2511 STT_state = wx.StaticText(PNL_form, -1, _('Region'))
2512 STT_state.SetForegroundColour('orange')
2513 self.PRW_state = cStateSelectionPhraseWheel(parent=PNL_form, id=-1)
2514 self.PRW_state.SetToolTipString(_("primary/home address: state/province/county/..."))
2515
2516 # country
2517 STT_country = wx.StaticText(PNL_form, -1, _('Country'))
2518 STT_country.SetForegroundColour('orange')
2519 self.PRW_country = cCountryPhraseWheel(parent = PNL_form, id = -1)
2520 self.PRW_country.SetToolTipString(_("primary/home address: country"))
2521
2522 # phone
2523 STT_phone = wx.StaticText(PNL_form, -1, _('Phone'))
2524 self.TTC_phone = wx.TextCtrl(PNL_form, -1)
2525 self.TTC_phone.SetToolTipString(_("phone number at home"))
2526
2527 # occupation
2528 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
2529 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
2530
2531 # comment
2532 STT_comment = wx.StaticText(PNL_form, -1, _('Comment'))
2533 self.TCTRL_comment = wx.TextCtrl(PNL_form, -1)
2534 self.TCTRL_comment.SetToolTipString(_('A comment on this patient.'))
2535
2536 # form main validator
2537 self.form_DTD = cFormDTD(fields = self.__class__.form_fields)
2538 PNL_form.SetValidator(cBasicPatDetailsPageValidator(dtd = self.form_DTD))
2539
2540 # layout input widgets
2541 SZR_input = wx.FlexGridSizer(cols = 2, rows = 16, vgap = 4, hgap = 4)
2542 SZR_input.AddGrowableCol(1)
2543 SZR_input.Add(STT_lastname, 0, wx.SHAPED)
2544 SZR_input.Add(self.PRW_lastname, 1, wx.EXPAND)
2545 SZR_input.Add(STT_firstname, 0, wx.SHAPED)
2546 SZR_input.Add(self.PRW_firstname, 1, wx.EXPAND)
2547 SZR_input.Add(STT_nick, 0, wx.SHAPED)
2548 SZR_input.Add(self.PRW_nick, 1, wx.EXPAND)
2549 SZR_input.Add(STT_dob, 0, wx.SHAPED)
2550 SZR_input.Add(self.PRW_dob, 1, wx.EXPAND)
2551 SZR_input.Add(STT_gender, 0, wx.SHAPED)
2552 SZR_input.Add(self.PRW_gender, 1, wx.EXPAND)
2553 SZR_input.Add(STT_title, 0, wx.SHAPED)
2554 SZR_input.Add(self.PRW_title, 1, wx.EXPAND)
2555 SZR_input.Add(STT_zip_code, 0, wx.SHAPED)
2556 SZR_input.Add(self.PRW_zip_code, 1, wx.EXPAND)
2557 SZR_input.Add(STT_street, 0, wx.SHAPED)
2558 SZR_input.Add(self.PRW_street, 1, wx.EXPAND)
2559 SZR_input.Add(STT_address_number, 0, wx.SHAPED)
2560 SZR_input.Add(self.TTC_address_number, 1, wx.EXPAND)
2561 SZR_input.Add(STT_town, 0, wx.SHAPED)
2562 SZR_input.Add(self.PRW_town, 1, wx.EXPAND)
2563 SZR_input.Add(STT_state, 0, wx.SHAPED)
2564 SZR_input.Add(self.PRW_state, 1, wx.EXPAND)
2565 SZR_input.Add(STT_country, 0, wx.SHAPED)
2566 SZR_input.Add(self.PRW_country, 1, wx.EXPAND)
2567 SZR_input.Add(STT_phone, 0, wx.SHAPED)
2568 SZR_input.Add(self.TTC_phone, 1, wx.EXPAND)
2569 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
2570 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
2571 SZR_input.Add(STT_comment, 0, wx.SHAPED)
2572 SZR_input.Add(self.TCTRL_comment, 1, wx.EXPAND)
2573
2574 PNL_form.SetSizerAndFit(SZR_input)
2575
2576 # layout page
2577 SZR_main = gmGuiHelpers.makePageTitle(self, self.__title)
2578 SZR_main.Add(PNL_form, 1, wx.EXPAND)
2579 #--------------------------------------------------------
2580 # event handling
2581 #--------------------------------------------------------
2583 self.PRW_firstname.add_callback_on_lose_focus(self.on_name_set)
2584 self.PRW_country.add_callback_on_selection(self.on_country_selected)
2585 self.PRW_zip_code.add_callback_on_lose_focus(self.on_zip_set)
2586 #--------------------------------------------------------
2588 """Set the states according to entered country."""
2589 self.PRW_state.set_context(context=u'country', val=data)
2590 return True
2591 #--------------------------------------------------------
2593 """Set the gender according to entered firstname.
2594
2595 Matches are fetched from existing records in backend.
2596 """
2597 firstname = self.PRW_firstname.GetValue().strip()
2598 rows, idx = gmPG2.run_ro_queries(queries = [{
2599 'cmd': u"select gender from dem.name_gender_map where name ilike %s",
2600 'args': [firstname]
2601 }])
2602 if len(rows) == 0:
2603 return True
2604 wx.CallAfter(self.PRW_gender.SetData, rows[0][0])
2605 return True
2606 #--------------------------------------------------------
2608 """Set the street, town, state and country according to entered zip code."""
2609 zip_code = self.PRW_zip_code.GetValue().strip()
2610 self.PRW_street.set_context(context=u'zip', val=zip_code)
2611 self.PRW_town.set_context(context=u'zip', val=zip_code)
2612 self.PRW_state.set_context(context=u'zip', val=zip_code)
2613 self.PRW_country.set_context(context=u'zip', val=zip_code)
2614 return True
2615 #============================================================
2617 """
2618 Wizard to create a new patient.
2619
2620 TODO:
2621 - write pages for different "themes" of patient creation
2622 - make it configurable which pages are loaded
2623 - make available sets of pages that apply to a country
2624 - make loading of some pages depend upon values in earlier pages, eg
2625 when the patient is female and older than 13 include a page about
2626 "female" data (number of kids etc)
2627
2628 FIXME: use: wizard.FindWindowById(wx.ID_FORWARD).Disable()
2629 """
2630 #--------------------------------------------------------
2631 - def __init__(self, parent, title = _('Register new person'), subtitle = _('Basic demographic details') ):
2632 """
2633 Creates a new instance of NewPatientWizard
2634 @param parent - The parent widget
2635 @type parent - A wx.Window instance
2636 """
2637 id_wiz = wx.NewId()
2638 wx.wizard.Wizard.__init__(self, parent, id_wiz, title) #images.getWizTest1Bitmap()
2639 self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY)
2640 self.__subtitle = subtitle
2641 self.__do_layout()
2642 #--------------------------------------------------------
2644 """Create new patient.
2645
2646 activate, too, if told to do so (and patient successfully created)
2647 """
2648 while True:
2649
2650 if not wx.wizard.Wizard.RunWizard(self, self.basic_pat_details):
2651 return False
2652
2653 try:
2654 # retrieve DTD and create patient
2655 ident = create_identity_from_dtd(dtd = self.basic_pat_details.form_DTD)
2656 except:
2657 _log.exception('cannot add new patient - missing identity fields')
2658 gmGuiHelpers.gm_show_error (
2659 _('Cannot create new patient.\n'
2660 'Missing parts of the identity.'
2661 ),
2662 _('Adding new patient')
2663 )
2664 continue
2665
2666 update_identity_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2667
2668 try:
2669 link_contacts_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2670 except:
2671 _log.exception('cannot finalize new patient - missing address fields')
2672 gmGuiHelpers.gm_show_error (
2673 _('Cannot add address for the new patient.\n'
2674 'You must either enter all of the address fields or\n'
2675 'none at all. The relevant fields are marked in yellow.\n'
2676 '\n'
2677 'You will need to add the address details in the\n'
2678 'demographics module.'
2679 ),
2680 _('Adding new patient')
2681 )
2682 break
2683
2684 link_occupation_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD)
2685
2686 break
2687
2688 if activate:
2689 from Gnumed.wxpython import gmPatSearchWidgets
2690 gmPatSearchWidgets.set_active_patient(patient = ident)
2691
2692 return ident
2693 #--------------------------------------------------------
2694 # internal helpers
2695 #--------------------------------------------------------
2697 """Arrange widgets.
2698 """
2699 # Create the wizard pages
2700 self.basic_pat_details = cBasicPatDetailsPage(self, self.__subtitle )
2701 self.FitToPage(self.basic_pat_details)
2702 #============================================================
2704 """
2705 This validator is used to ensure that the user has entered all
2706 the required conditional values in the page (eg., to properly
2707 create an address, all the related fields must be filled).
2708 """
2709 #--------------------------------------------------------
2711 """
2712 Validator initialization.
2713 @param dtd The object containing the data model.
2714 @type dtd A cFormDTD instance
2715 """
2716 # initialize parent class
2717 wx.PyValidator.__init__(self)
2718 # validator's storage object
2719 self.form_DTD = dtd
2720 #--------------------------------------------------------
2722 """
2723 Standard cloner.
2724 Note that every validator must implement the Clone() method.
2725 """
2726 return cBasicPatDetailsPageValidator(dtd = self.form_DTD) # FIXME: probably need new instance of DTD ?
2727 #--------------------------------------------------------
2729 """
2730 Validate the contents of the given text control.
2731 """
2732 _pnl_form = self.GetWindow().GetParent()
2733
2734 error = False
2735
2736 # name fields
2737 if _pnl_form.PRW_lastname.GetValue().strip() == '':
2738 error = True
2739 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.'))
2740 _pnl_form.PRW_lastname.SetBackgroundColour('pink')
2741 _pnl_form.PRW_lastname.Refresh()
2742 else:
2743 _pnl_form.PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2744 _pnl_form.PRW_lastname.Refresh()
2745
2746 if _pnl_form.PRW_firstname.GetValue().strip() == '':
2747 error = True
2748 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.'))
2749 _pnl_form.PRW_firstname.SetBackgroundColour('pink')
2750 _pnl_form.PRW_firstname.Refresh()
2751 else:
2752 _pnl_form.PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2753 _pnl_form.PRW_firstname.Refresh()
2754
2755 # gender
2756 if _pnl_form.PRW_gender.GetData() is None:
2757 error = True
2758 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.'))
2759 _pnl_form.PRW_gender.SetBackgroundColour('pink')
2760 _pnl_form.PRW_gender.Refresh()
2761 else:
2762 _pnl_form.PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2763 _pnl_form.PRW_gender.Refresh()
2764
2765 # dob validation
2766 if (
2767 (_pnl_form.PRW_dob.GetValue().strip() == u'')
2768 or (not _pnl_form.PRW_dob.is_valid_timestamp())
2769 or (_pnl_form.PRW_dob.GetData().timestamp.year < 1900)
2770 ):
2771 error = True
2772 msg = _('Cannot parse <%s> into proper timestamp.') % _pnl_form.PRW_dob.GetValue()
2773 gmDispatcher.send(signal = 'statustext', msg = msg)
2774 _pnl_form.PRW_dob.SetBackgroundColour('pink')
2775 _pnl_form.PRW_dob.Refresh()
2776 else:
2777 _pnl_form.PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2778 _pnl_form.PRW_dob.Refresh()
2779
2780 # address
2781 is_any_field_filled = False
2782 address_fields = (
2783 _pnl_form.TTC_address_number,
2784 _pnl_form.PRW_zip_code,
2785 _pnl_form.PRW_street,
2786 _pnl_form.PRW_town
2787 )
2788 for field in address_fields:
2789 if field.GetValue().strip() == u'':
2790 if is_any_field_filled:
2791 error = True
2792 msg = _('To properly create an address, all the related fields must be filled in.')
2793 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2794 field.SetBackgroundColour('pink')
2795 field.SetFocus()
2796 field.Refresh()
2797 else:
2798 is_any_field_filled = True
2799 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2800 field.Refresh()
2801
2802 address_fields = (
2803 _pnl_form.PRW_state,
2804 _pnl_form.PRW_country
2805 )
2806 for field in address_fields:
2807 if field.GetData() is None:
2808 if is_any_field_filled:
2809 error = True
2810 msg = _('To properly create an address, all the related fields must be filled in.')
2811 gmGuiHelpers.gm_show_error(msg, _('Required fields'))
2812 field.SetBackgroundColour('pink')
2813 field.SetFocus()
2814 field.Refresh()
2815 else:
2816 is_any_field_filled = True
2817 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2818 field.Refresh()
2819
2820 return (not error)
2821 #--------------------------------------------------------
2823 """
2824 Transfer data from validator to window.
2825 The default implementation returns False, indicating that an error
2826 occurred. We simply return True, as we don't do any data transfer.
2827 """
2828 _pnl_form = self.GetWindow().GetParent()
2829 # fill in controls with values from self.form_DTD
2830 _pnl_form.PRW_gender.SetData(self.form_DTD['gender'])
2831 _pnl_form.PRW_dob.SetText(self.form_DTD['dob'])
2832 _pnl_form.PRW_lastname.SetText(self.form_DTD['lastnames'])
2833 _pnl_form.PRW_firstname.SetText(self.form_DTD['firstnames'])
2834 _pnl_form.PRW_title.SetText(self.form_DTD['title'])
2835 _pnl_form.PRW_nick.SetText(self.form_DTD['nick'])
2836 _pnl_form.PRW_occupation.SetText(self.form_DTD['occupation'])
2837 _pnl_form.TTC_address_number.SetValue(self.form_DTD['address_number'])
2838 _pnl_form.PRW_street.SetText(self.form_DTD['street'])
2839 _pnl_form.PRW_zip_code.SetText(self.form_DTD['zip_code'])
2840 _pnl_form.PRW_town.SetText(self.form_DTD['town'])
2841 _pnl_form.PRW_state.SetData(self.form_DTD['state'])
2842 _pnl_form.PRW_country.SetData(self.form_DTD['country'])
2843 _pnl_form.TTC_phone.SetValue(self.form_DTD['phone'])
2844 _pnl_form.TCTRL_comment.SetValue(self.form_DTD['comment'])
2845 return True # Prevent wxDialog from complaining
2846 #--------------------------------------------------------
2848 """
2849 Transfer data from window to validator.
2850 The default implementation returns False, indicating that an error
2851 occurred. We simply return True, as we don't do any data transfer.
2852 """
2853 # FIXME: should be called automatically
2854 if not self.GetWindow().GetParent().Validate():
2855 return False
2856 try:
2857 _pnl_form = self.GetWindow().GetParent()
2858 # fill in self.form_DTD with values from controls
2859 self.form_DTD['gender'] = _pnl_form.PRW_gender.GetData()
2860 self.form_DTD['dob'] = _pnl_form.PRW_dob.GetData()
2861
2862 self.form_DTD['lastnames'] = _pnl_form.PRW_lastname.GetValue()
2863 self.form_DTD['firstnames'] = _pnl_form.PRW_firstname.GetValue()
2864 self.form_DTD['title'] = _pnl_form.PRW_title.GetValue()
2865 self.form_DTD['nick'] = _pnl_form.PRW_nick.GetValue()
2866
2867 self.form_DTD['occupation'] = _pnl_form.PRW_occupation.GetValue()
2868
2869 self.form_DTD['address_number'] = _pnl_form.TTC_address_number.GetValue()
2870 self.form_DTD['street'] = _pnl_form.PRW_street.GetValue()
2871 self.form_DTD['zip_code'] = _pnl_form.PRW_zip_code.GetValue()
2872 self.form_DTD['town'] = _pnl_form.PRW_town.GetValue()
2873 self.form_DTD['state'] = _pnl_form.PRW_state.GetData()
2874 self.form_DTD['country'] = _pnl_form.PRW_country.GetData()
2875
2876 self.form_DTD['phone'] = _pnl_form.TTC_phone.GetValue()
2877
2878 self.form_DTD['comment'] = _pnl_form.TCTRL_comment.GetValue()
2879 except:
2880 return False
2881 return True
2882 #============================================================
2884 """
2885 Simple Data Transfer Dictionary class to make easy the trasfer of
2886 data between the form (view) and the business logic.
2887
2888 Maybe later consider turning this into a standard dict by
2889 {}.fromkeys([key, key, ...], default) when it becomes clear that
2890 we really don't need the added potential of a full-fledged class.
2891 """
2893 """
2894 Initialize the DTD with the supplied field names.
2895 @param fields The names of the fields.
2896 @type fields A TupleType instance.
2897 """
2898 self.data = {}
2899 for a_field in fields:
2900 self.data[a_field] = ''
2901
2903 """
2904 Retrieve the value of the given attribute (key)
2905 @param attribute The attribute (key) to retrieve its value for.
2906 @type attribute a StringType instance.
2907 """
2908 if not self.data[attribute]:
2909 return ''
2910 return self.data[attribute]
2911
2913 """
2914 Set the value of a given attribute (key).
2915 @param attribute The attribute (key) to set its value for.
2916 @type attribute a StringType instance.
2917 @param avaluee The value to set.
2918 @rtpe attribute a StringType instance.
2919 """
2920 self.data[attribute] = value
2921
2927 #============================================================
2928 # patient demographics editing classes
2929 #============================================================
2931 """Notebook displaying demographics editing pages:
2932
2933 - Identity
2934 - Contacts (addresses, phone numbers, etc)
2935
2936 Does NOT act on/listen to the current patient.
2937 """
2938 #--------------------------------------------------------
2940
2941 wx.Notebook.__init__ (
2942 self,
2943 parent = parent,
2944 id = id,
2945 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER,
2946 name = self.__class__.__name__
2947 )
2948
2949 self.__identity = None
2950 self.__do_layout()
2951 self.SetSelection(0)
2952 #--------------------------------------------------------
2953 # public API
2954 #--------------------------------------------------------
2956 """Populate fields in pages with data from model."""
2957 for page_idx in range(self.GetPageCount()):
2958 page = self.GetPage(page_idx)
2959 page.identity = self.__identity
2960
2961 return True
2962 #--------------------------------------------------------
2963 # internal API
2964 #--------------------------------------------------------
2966 """Build patient edition notebook pages."""
2967 # contacts page
2968 new_page = cPersonContactsManagerPnl(self, -1)
2969 new_page.identity = self.__identity
2970 self.AddPage (
2971 page = new_page,
2972 text = _('Contacts'),
2973 select = True
2974 )
2975
2976 # identity page
2977 new_page = cPersonIdentityManagerPnl(self, -1)
2978 new_page.identity = self.__identity
2979 self.AddPage (
2980 page = new_page,
2981 text = _('Identity'),
2982 select = False
2983 )
2984 #--------------------------------------------------------
2985 # properties
2986 #--------------------------------------------------------
2989
2991 self.__identity = identity
2992
2993 identity = property(_get_identity, _set_identity)
2994 #============================================================
2995 # FIXME: support multiple occupations
2996 # FIXME: redo with wxGlade
2997
2999 """Page containing patient occupations edition fields.
3000 """
3002 """
3003 Creates a new instance of BasicPatDetailsPage
3004 @param parent - The parent widget
3005 @type parent - A wx.Window instance
3006 @param id - The widget id
3007 @type id - An integer
3008 """
3009 wx.Panel.__init__(self, parent, id)
3010 self.__ident = ident
3011 self.__do_layout()
3012 #--------------------------------------------------------
3014 PNL_form = wx.Panel(self, -1)
3015 # occupation
3016 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation'))
3017 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1)
3018 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient"))
3019 # known since
3020 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated'))
3021 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY)
3022
3023 # layout input widgets
3024 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4)
3025 SZR_input.AddGrowableCol(1)
3026 SZR_input.Add(STT_occupation, 0, wx.SHAPED)
3027 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND)
3028 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED)
3029 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND)
3030 PNL_form.SetSizerAndFit(SZR_input)
3031
3032 # layout page
3033 SZR_main = wx.BoxSizer(wx.VERTICAL)
3034 SZR_main.Add(PNL_form, 1, wx.EXPAND)
3035 self.SetSizer(SZR_main)
3036 #--------------------------------------------------------
3039 #--------------------------------------------------------
3041 if identity is not None:
3042 self.__ident = identity
3043 jobs = self.__ident.get_occupations()
3044 if len(jobs) > 0:
3045 self.PRW_occupation.SetText(jobs[0]['l10n_occupation'])
3046 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y'))
3047 return True
3048 #--------------------------------------------------------
3050 if self.PRW_occupation.IsModified():
3051 new_job = self.PRW_occupation.GetValue().strip()
3052 jobs = self.__ident.get_occupations()
3053 for job in jobs:
3054 if job['l10n_occupation'] == new_job:
3055 continue
3056 self.__ident.unlink_occupation(occupation = job['l10n_occupation'])
3057 self.__ident.link_occupation(occupation = new_job)
3058 return True
3059 #============================================================
3061 """Patient demographics plugin for main notebook.
3062
3063 Hosts another notebook with pages for Identity, Contacts, etc.
3064
3065 Acts on/listens to the currently active patient.
3066 """
3067 #--------------------------------------------------------
3069 wx.Panel.__init__ (self, parent = parent, id = id, style = wx.NO_BORDER)
3070 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
3071 self.__do_layout()
3072 self.__register_interests()
3073 #--------------------------------------------------------
3074 # public API
3075 #--------------------------------------------------------
3076 #--------------------------------------------------------
3077 # internal helpers
3078 #--------------------------------------------------------
3080 """Arrange widgets."""
3081 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1)
3082
3083 szr_main = wx.BoxSizer(wx.VERTICAL)
3084 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND)
3085 self.SetSizerAndFit(szr_main)
3086 #--------------------------------------------------------
3087 # event handling
3088 #--------------------------------------------------------
3090 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
3091 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
3092 #--------------------------------------------------------
3095 #--------------------------------------------------------
3098 #--------------------------------------------------------
3099 # reget mixin API
3100 #--------------------------------------------------------
3110 #============================================================
3112 """
3113 Register a new patient, given the data supplied in the
3114 Data Transfer Dictionary object.
3115
3116 @param basic_details_DTD Data Transfer Dictionary encapsulating all the
3117 supplied data.
3118 @type basic_details_DTD A cFormDTD instance.
3119 """
3120 new_identity = gmPerson.create_identity (
3121 gender = dtd['gender'],
3122 dob = dtd['dob'].get_pydt(),
3123 lastnames = dtd['lastnames'],
3124 firstnames = dtd['firstnames']
3125 )
3126 if new_identity is None:
3127 _log.error('cannot create identity from %s' % str(dtd))
3128 return None
3129 _log.debug('identity created: %s' % new_identity)
3130
3131 if dtd['comment'] is not None:
3132 if dtd['comment'].strip() != u'':
3133 name = new_identity.get_active_name()
3134 name['comment'] = dtd['comment']
3135 name.save_payload()
3136
3137 return new_identity
3138 #============================================================
3140 """
3141 Update patient details with data supplied by
3142 Data Transfer Dictionary object.
3143
3144 @param basic_details_DTD Data Transfer Dictionary encapsulating all the
3145 supplied data.
3146 @type basic_details_DTD A cFormDTD instance.
3147 """
3148 # identity
3149 if identity['gender'] != dtd['gender']:
3150 identity['gender'] = dtd['gender']
3151 if identity['dob'] != dtd['dob'].get_pydt():
3152 identity['dob'] = dtd['dob'].get_pydt()
3153 if len(dtd['title']) > 0 and identity['title'] != dtd['title']:
3154 identity['title'] = dtd['title']
3155 # FIXME: error checking
3156 # FIXME: we need a trigger to update the values of the
3157 # view, identity['keys'], eg. lastnames and firstnames
3158 # are not refreshed.
3159 identity.save_payload()
3160
3161 # names
3162 # FIXME: proper handling of "active"
3163 if identity['firstnames'] != dtd['firstnames'] or identity['lastnames'] != dtd['lastnames']:
3164 new_name = identity.add_name(firstnames = dtd['firstnames'], lastnames = dtd['lastnames'], active = True)
3165 # nickname
3166 if len(dtd['nick']) > 0 and identity['preferred'] != dtd['nick']:
3167 identity.set_nickname(nickname = dtd['nick'])
3168
3169 return True
3170 #============================================================
3172 """
3173 Update patient details with data supplied by
3174 Data Transfer Dictionary object.
3175
3176 @param basic_details_DTD Data Transfer Dictionary encapsulating all the
3177 supplied data.
3178 @type basic_details_DTD A cFormDTD instance.
3179 """
3180 lng = len (
3181 dtd['address_number'].strip() +
3182 dtd['street'].strip() +
3183 dtd['zip_code'].strip() +
3184 dtd['town'].strip() +
3185 dtd['state'].strip() +
3186 dtd['country'].strip()
3187 )
3188 # FIXME: improve error checking
3189 if lng > 5:
3190 # FIXME: support address type
3191 success = identity.link_address (
3192 number = dtd['address_number'].strip(),
3193 street = dtd['street'].strip(),
3194 postcode = dtd['zip_code'].strip(),
3195 urb = dtd['town'].strip(),
3196 state = dtd['state'].strip(),
3197 country = dtd['country'].strip()
3198 )
3199 if not success:
3200 gmDispatcher.send(signal='statustext', msg = _('Cannot add patient address.'))
3201 else:
3202 gmDispatcher.send(signal='statustext', msg = _('Cannot add patient address. Missing fields.'))
3203
3204 if len(dtd['phone']) > 0:
3205 identity.link_comm_channel (
3206 comm_medium = 'homephone',
3207 url = dtd['phone'],
3208 is_confidential = False
3209 )
3210
3211 # FIXME: error checking
3212 # identity.save_payload()
3213 return True
3214 #============================================================
3216 """
3217 Update patient details with data supplied by
3218 Data Transfer Dictionary object.
3219
3220 @param basic_details_DTD Data Transfer Dictionary encapsulating all the
3221 supplied data.
3222 @type basic_details_DTD A cFormDTD instance.
3223 """
3224 identity.link_occupation(occupation = dtd['occupation'])
3225
3226 return True
3227 #============================================================
3229 """
3230 Utility class to test the new patient wizard.
3231 """
3232 #--------------------------------------------------------
3234 """
3235 Create a new instance of TestPanel.
3236 @param parent The parent widget
3237 @type parent A wx.Window instance
3238 """
3239 wx.Panel.__init__(self, parent, id)
3240 wizard = cNewPatientWizard(self)
3241 print wizard.RunWizard()
3242 #============================================================
3243 if __name__ == "__main__":
3244
3245 #--------------------------------------------------------
3247 app = wx.PyWidgetTester(size = (200, 50))
3248 pw = cZipcodePhraseWheel(app.frame, -1)
3249 app.frame.Show(True)
3250 app.MainLoop()
3251 #--------------------------------------------------------
3253 app = wx.PyWidgetTester(size = (200, 50))
3254 pw = cStateSelectionPhraseWheel(app.frame, -1)
3255 # pw.set_context(context = u'zip', val = u'04318')
3256 # pw.set_context(context = u'country', val = u'Deutschland')
3257 app.frame.Show(True)
3258 app.MainLoop()
3259 #--------------------------------------------------------
3261 app = wx.PyWidgetTester(size = (200, 50))
3262 pw = cUrbPhraseWheel(app.frame, -1)
3263 app.frame.Show(True)
3264 pw.set_context(context = u'zip', val = u'04317')
3265 app.MainLoop()
3266 #--------------------------------------------------------
3268 app = wx.PyWidgetTester(size = (200, 50))
3269 pw = cSuburbPhraseWheel(app.frame, -1)
3270 app.frame.Show(True)
3271 app.MainLoop()
3272 #--------------------------------------------------------
3274 app = wx.PyWidgetTester(size = (200, 50))
3275 pw = cAddressTypePhraseWheel(app.frame, -1)
3276 app.frame.Show(True)
3277 app.MainLoop()
3278 #--------------------------------------------------------
3280 app = wx.PyWidgetTester(size = (200, 50))
3281 pw = cAddressPhraseWheel(app.frame, -1)
3282 app.frame.Show(True)
3283 app.MainLoop()
3284 #--------------------------------------------------------
3286 app = wx.PyWidgetTester(size = (200, 50))
3287 pw = cStreetPhraseWheel(app.frame, -1)
3288 # pw.set_context(context = u'zip', val = u'04318')
3289 app.frame.Show(True)
3290 app.MainLoop()
3291 #--------------------------------------------------------
3293 app = wx.PyWidgetTester(size = (600, 400))
3294 app.SetWidget(cKOrganizerSchedulePnl)
3295 app.MainLoop()
3296 #--------------------------------------------------------
3298 app = wx.PyWidgetTester(size = (600, 400))
3299 widget = cPersonNamesManagerPnl(app.frame, -1)
3300 widget.identity = activate_patient()
3301 app.frame.Show(True)
3302 app.MainLoop()
3303 #--------------------------------------------------------
3305 app = wx.PyWidgetTester(size = (600, 400))
3306 widget = cPersonIDsManagerPnl(app.frame, -1)
3307 widget.identity = activate_patient()
3308 app.frame.Show(True)
3309 app.MainLoop()
3310 #--------------------------------------------------------
3312 app = wx.PyWidgetTester(size = (600, 400))
3313 widget = cPersonIdentityManagerPnl(app.frame, -1)
3314 widget.identity = activate_patient()
3315 app.frame.Show(True)
3316 app.MainLoop()
3317 #--------------------------------------------------------
3319 app = wx.PyWidgetTester(size = (600, 400))
3320 app.SetWidget(cNameGenderDOBEditAreaPnl, name = activate_patient().get_active_name())
3321 app.MainLoop()
3322 #--------------------------------------------------------
3324 app = wx.PyWidgetTester(size = (600, 400))
3325 app.SetWidget(cAddressEditAreaPnl, address = gmDemographicRecord.cAddress(aPK_obj = 1))
3326 app.MainLoop()
3327 #--------------------------------------------------------
3329 app = wx.PyWidgetTester(size = (600, 400))
3330 widget = cPersonAddressesManagerPnl(app.frame, -1)
3331 widget.identity = activate_patient()
3332 app.frame.Show(True)
3333 app.MainLoop()
3334 #--------------------------------------------------------
3336 app = wx.PyWidgetTester(size = (600, 400))
3337 widget = cPersonCommsManagerPnl(app.frame, -1)
3338 widget.identity = activate_patient()
3339 app.frame.Show(True)
3340 app.MainLoop()
3341 #--------------------------------------------------------
3343 app = wx.PyWidgetTester(size = (600, 400))
3344 widget = cPersonContactsManagerPnl(app.frame, -1)
3345 widget.identity = activate_patient()
3346 app.frame.Show(True)
3347 app.MainLoop()
3348 #--------------------------------------------------------
3350 app = wx.PyWidgetTester(size = (600, 400))
3351 widget = cPersonDemographicsEditorNb(app.frame, -1)
3352 widget.identity = activate_patient()
3353 widget.refresh()
3354 app.frame.Show(True)
3355 app.MainLoop()
3356 #--------------------------------------------------------
3358 patient = gmPerson.ask_for_patient()
3359 if patient is None:
3360 print "No patient. Exiting gracefully..."
3361 sys.exit(0)
3362 from Gnumed.wxpython import gmPatSearchWidgets
3363 gmPatSearchWidgets.set_active_patient(patient=patient)
3364 return patient
3365 #--------------------------------------------------------
3366 if len(sys.argv) > 1 and sys.argv[1] == 'test':
3367
3368 gmI18N.activate_locale()
3369 gmI18N.install_domain(domain='gnumed')
3370 gmPG2.get_connection()
3371
3372 # a = cFormDTD(fields = cBasicPatDetailsPage.form_fields)
3373
3374 # app = wx.PyWidgetTester(size = (400, 300))
3375 # app.SetWidget(cNotebookedPatEditionPanel, -1)
3376 # app.SetWidget(TestWizardPanel, -1)
3377 # app.frame.Show(True)
3378 # app.MainLoop()
3379
3380 # phrasewheels
3381 # test_zipcode_prw()
3382 # test_state_prw()
3383 # test_street_prw()
3384 # test_organizer_pnl()
3385 #test_address_type_prw()
3386 #test_suburb_prw()
3387 test_urb_prw()
3388 #test_address_prw()
3389
3390 # contacts related widgets
3391 #test_address_ea_pnl()
3392 #test_person_adrs_pnl()
3393 #test_person_comms_pnl()
3394 #test_pat_contacts_pnl()
3395
3396 # identity related widgets
3397 #test_person_names_pnl()
3398 #test_person_ids_pnl()
3399 #test_pat_ids_pnl()
3400 #test_name_ea_pnl()
3401
3402 #test_cPersonDemographicsEditorNb()
3403
3404 #============================================================
3405 # $Log: gmDemographicsWidgets.py,v $
3406 # Revision 1.175 2010/02/07 15:13:02 ncq
3407 # - patient -> person
3408 #
3409 # Revision 1.174 2010/01/31 18:14:40 ncq
3410 # - configure-default-region/country()
3411 # - improved province management list layout
3412 # - use default region/country in new patient creation
3413 #
3414 # Revision 1.173 2010/01/08 14:39:44 ncq
3415 # - support NULLing the dob
3416 #
3417 # Revision 1.172 2010/01/08 13:54:19 ncq
3418 # - support external ID in new-patient widget
3419 #
3420 # Revision 1.171 2009/11/29 15:58:18 ncq
3421 # - cleanup
3422 #
3423 # Revision 1.170 2009/11/18 16:10:58 ncq
3424 # - sufficiently complete provinces management
3425 #
3426 # Revision 1.169 2009/11/17 19:42:12 ncq
3427 # - further implement province management
3428 #
3429 # Revision 1.168 2009/07/23 16:38:33 ncq
3430 # - cleanup
3431 # - rewrite address valid for save and use it better
3432 # - catch link_address exceptions
3433 #
3434 # Revision 1.167 2009/06/29 15:05:24 ncq
3435 # - new person widget:
3436 # - set focus to last name
3437 # - raise demographics plugin after adding new person
3438 # - improved DOB validation
3439 # - improved address validation
3440 #
3441 # Revision 1.166 2009/06/20 22:33:32 ncq
3442 # - improved warning on disabling person
3443 #
3444 # Revision 1.165 2009/06/20 12:35:49 ncq
3445 # - switch Identity and Contacts page as per list discussion
3446 #
3447 # Revision 1.164 2009/06/04 15:22:35 ncq
3448 # - re-import allowing saving person w/o DOB and
3449 # use appropriate set_active_patient()
3450 #
3451 # Revision 1.164 2009/05/28 10:53:16 ncq
3452 # - allow saving person without DOB
3453 #
3454 # Revision 1.163 2009/04/24 13:01:13 ncq
3455 # - need to use code on state/country
3456 #
3457 # Revision 1.162 2009/04/24 12:32:38 ncq
3458 # - fix a typo
3459 #
3460 # Revision 1.161 2009/04/24 12:08:42 ncq
3461 # - factor out address match provider to eventually make it smarter
3462 # - apply final regex to first/lastnames PRW
3463 # - implement validity check/saving for new patient EA
3464 #
3465 # Revision 1.160 2009/04/21 16:58:48 ncq
3466 # - address phrasewheel label improvement and get_address
3467 # - create_new_patient
3468 # - new-patient edit area
3469 #
3470 # Revision 1.159 2009/02/25 21:07:41 ncq
3471 # - catch exception when failing to save address
3472 #
3473 # Revision 1.158 2009/02/05 14:29:09 ncq
3474 # - verify DOB > 1900
3475 #
3476 # Revision 1.157 2009/01/15 11:35:41 ncq
3477 # - cleanup
3478 #
3479 # Revision 1.156 2008/11/21 13:05:48 ncq
3480 # - disallow deleting the only name of a person
3481 #
3482 # Revision 1.155 2008/11/20 18:48:55 ncq
3483 # - fix overly zealous validation when creating external IDs
3484 #
3485 # Revision 1.154 2008/08/28 18:33:02 ncq
3486 # - inform user on KOrganizer not being callable
3487 #
3488 # Revision 1.153 2008/08/15 16:01:06 ncq
3489 # - start managing provinces
3490 # - orange-mark address fields in wizard
3491 # - better save error handling in wizard
3492 #
3493 # Revision 1.152 2008/07/07 13:43:16 ncq
3494 # - current patient .connected
3495 #
3496 # Revision 1.151 2008/06/15 20:34:31 ncq
3497 # - adjust to match provider properties
3498 #
3499 # Revision 1.150 2008/06/09 15:33:31 ncq
3500 # - much improved sanity check when saving/editing patient address
3501 #
3502 # Revision 1.149 2008/05/20 16:43:25 ncq
3503 # - improve match provider SQL for urb phrasewheel
3504 #
3505 # Revision 1.148 2008/05/14 13:45:48 ncq
3506 # - Directions -> Street info
3507 # - Postcode -> Postal code
3508 # - Town -> Place
3509 # - State -> Region
3510 # - fix phrasewheel SQL
3511 # - test for urb phrasewheel
3512 #
3513 # Revision 1.147 2008/05/13 14:11:21 ncq
3514 # - support comment on new patient
3515 #
3516 # Revision 1.146 2008/03/05 22:30:13 ncq
3517 # - new style logging
3518 #
3519 # Revision 1.145 2008/02/26 16:26:05 ncq
3520 # - actually fail on detecting error on saving comm channel
3521 # - add some tooltips
3522 #
3523 # Revision 1.144 2008/02/25 17:39:48 ncq
3524 # - improve error checking for comm channel saving
3525 #
3526 # Revision 1.143 2008/01/27 21:13:50 ncq
3527 # - change a few labels per Jim
3528 #
3529 # Revision 1.142 2008/01/14 20:40:09 ncq
3530 # - don't crash on missing korganizer transfer file
3531 #
3532 # Revision 1.141 2008/01/07 19:52:26 ncq
3533 # - enable editing comm channels
3534 #
3535 # Revision 1.140 2008/01/05 16:41:27 ncq
3536 # - remove logging from gm_show_*()
3537 #
3538 # Revision 1.139 2007/12/23 12:10:30 ncq
3539 # - cleanup
3540 #
3541 # Revision 1.138 2007/12/11 12:49:25 ncq
3542 # - explicit signal handling
3543 #
3544 # Revision 1.137 2007/12/06 10:46:05 ncq
3545 # - improve external ID type phrasewheel
3546 # - in edit area on setting ext id type pre-set corresponding issuer if any
3547 #
3548 # Revision 1.136 2007/12/06 08:41:31 ncq
3549 # - improve address display
3550 # - better layout
3551 # - external ID phrasewheels and edit area
3552 #
3553 # Revision 1.135 2007/12/04 18:37:15 ncq
3554 # - edit_occupation()
3555 # - cleanup
3556 #
3557 # Revision 1.134 2007/12/04 16:16:27 ncq
3558 # - use gmAuthWidgets
3559 #
3560 # Revision 1.133 2007/12/03 20:44:14 ncq
3561 # - use delete_name()
3562 #
3563 # Revision 1.132 2007/12/02 21:00:45 ncq
3564 # - cAddressPhraseWheel
3565 # - cCommChannelTypePhraseWheel
3566 # - cCommChannelEditAreaPnl
3567 # - use thereof
3568 # - more tests
3569 #
3570 # Revision 1.131 2007/12/02 11:35:19 ncq
3571 # - in edit unlink old address if new one created
3572 #
3573 # Revision 1.130 2007/11/28 22:35:58 ncq
3574 # - make empty == None == NULL on nick/title/comment
3575 #
3576 # Revision 1.129 2007/11/28 14:00:10 ncq
3577 # - fix a few typos
3578 # - set titles on generic edit areas
3579 #
3580 # Revision 1.128 2007/11/28 11:56:13 ncq
3581 # - comments/wording improved, cleanup
3582 # - name/gender/dob edit area and use in person identity panel/notebook plugin
3583 # - more tests
3584 #
3585 # Revision 1.127 2007/11/17 16:36:59 ncq
3586 # - cPersonAddressesManagerPnl
3587 # - cPersonContactsManagerPnl
3588 # - cPersonCommsManagerPnl
3589 # - cAddressEditAreaPnl
3590 # - cAddressTypePhraseWheel
3591 # - cSuburbPhraseWheel
3592 # - more tests
3593 #
3594 # Revision 1.126 2007/08/28 14:18:12 ncq
3595 # - no more gm_statustext()
3596 #
3597 # Revision 1.125 2007/08/12 00:09:07 ncq
3598 # - no more gmSignals.py
3599 #
3600 # Revision 1.124 2007/07/22 09:04:44 ncq
3601 # - tmp/ now in .gnumed/
3602 #
3603 # Revision 1.123 2007/07/10 20:28:36 ncq
3604 # - consolidate install_domain() args
3605 #
3606 # Revision 1.122 2007/07/09 12:42:48 ncq
3607 # - KOrganizer panel
3608 #
3609 # Revision 1.121 2007/07/03 16:00:12 ncq
3610 # - nickname MAY start with lower case
3611 #
3612 # Revision 1.120 2007/05/21 22:30:12 ncq
3613 # - cleanup
3614 # - don't try to store empty address in link_contacts_from_dtd()
3615 #
3616 # Revision 1.119 2007/05/14 13:11:24 ncq
3617 # - use statustext() signal
3618 #
3619 # Revision 1.118 2007/04/02 18:39:52 ncq
3620 # - gmFuzzyTimestamp -> gmDateTime
3621 #
3622 # Revision 1.117 2007/03/31 21:34:11 ncq
3623 # - use gmPerson.set_active_patient()
3624 #
3625 # Revision 1.116 2007/02/22 17:41:13 ncq
3626 # - adjust to gmPerson changes
3627 #
3628 # Revision 1.115 2007/02/17 13:59:20 ncq
3629 # - honor entered occupation in new patient wizard
3630 #
3631 # Revision 1.114 2007/02/06 13:43:40 ncq
3632 # - no more aDelay in __init__()
3633 #
3634 # Revision 1.113 2007/02/05 12:15:23 ncq
3635 # - no more aMatchProvider/selection_only in cPhraseWheel.__init__()
3636 #
3637 # Revision 1.112 2007/02/04 15:52:10 ncq
3638 # - set proper CAPS modes on phrasewheels
3639 # - use SetText()
3640 # - remove HSCROLL/VSCROLL so we run on Mac
3641 #
3642 # Revision 1.111 2006/11/28 20:43:26 ncq
3643 # - remove lots of debugging prints
3644 #
3645 # Revision 1.110 2006/11/26 14:23:09 ncq
3646 # - add cOccupationPhraseWheel and use it
3647 # - display last modified on occupation entry
3648 #
3649 # Revision 1.109 2006/11/24 10:01:31 ncq
3650 # - gm_beep_statustext() -> gm_statustext()
3651 #
3652 # Revision 1.108 2006/11/20 16:01:35 ncq
3653 # - use gmTools.coalesce()
3654 # - some SetValue() -> SetData() fixes
3655 # - massively cleanup demographics edit notebook and consolidate save
3656 # logic, remove validator use as it was more pain than gain
3657 # - we now do not lower() inside strings anymore
3658 # - we now take a lot of care not to invalidate the DOB
3659 #
3660 # Revision 1.107 2006/11/07 23:53:30 ncq
3661 # - be ever more careful in handling DOBs, use get_pydt() on fuzzy timestamps
3662 #
3663 # Revision 1.106 2006/11/06 12:51:53 ncq
3664 # - a few u''s
3665 # - actually need to *pass* context to match providers, too
3666 # - adjust a few thresholds
3667 # - improved test suite
3668 #
3669 # Revision 1.105 2006/11/06 10:28:49 ncq
3670 # - zipcode/street/urb/country/lastname/firstname/nickname/title phrasewheels
3671 # - use them
3672 #
3673 # Revision 1.104 2006/11/05 17:55:33 ncq
3674 # - dtd['dob'] already is a timestamp
3675 #
3676 # Revision 1.103 2006/11/05 16:18:29 ncq
3677 # - cleanup, _() handling in test mode, sys.path handling in CVS mode
3678 # - add cStateSelectionPhraseWheel and use it
3679 # - try being more careful in contacts/identity editing such as not
3680 # to change gender/state/dob behind the back of the user
3681 #
3682 # Revision 1.102 2006/10/31 12:38:30 ncq
3683 # - stop improper capitalize_first()
3684 # - more gmPG -> gmPG2
3685 # - remove get_name_gender_map()
3686 #
3687 # Revision 1.101 2006/10/25 07:46:44 ncq
3688 # - Format() -> strftime() since datetime.datetime does not have .Format()
3689 #
3690 # Revision 1.100 2006/10/24 13:21:53 ncq
3691 # - gmPG -> gmPG2
3692 # - cMatchProvider_SQL2() does not need service name anymore
3693 #
3694 # Revision 1.99 2006/08/10 07:19:05 ncq
3695 # - remove import of gmPatientHolder
3696 #
3697 # Revision 1.98 2006/08/01 22:03:18 ncq
3698 # - cleanup
3699 # - add disable_identity()
3700 #
3701 # Revision 1.97 2006/07/21 21:34:04 ncq
3702 # - proper header/subheader for new *person* wizard (not *patient*)
3703 #
3704 # Revision 1.96 2006/07/19 20:29:50 ncq
3705 # - import cleanup
3706 #
3707 # Revision 1.95 2006/07/04 14:12:48 ncq
3708 # - add some phrasewheel sanity LIMITs
3709 # - use gender phrasewheel in pat modify, too
3710 #
3711 # Revision 1.94 2006/06/28 22:15:01 ncq
3712 # - make cGenderSelectionPhraseWheel self-sufficient and use it, too
3713 #
3714 # Revision 1.93 2006/06/28 14:09:17 ncq
3715 # - more cleanup
3716 # - add cGenderSelectionPhraseWheel() and start using it
3717 #
3718 # Revision 1.92 2006/06/20 10:04:40 ncq
3719 # - removed reams of crufty code
3720 #
3721 # Revision 1.91 2006/06/20 09:42:42 ncq
3722 # - cTextObjectValidator -> cTextWidgetValidator
3723 # - add custom invalid message to text widget validator
3724 # - variable renaming, cleanup
3725 # - fix demographics validation
3726 #
3727 # Revision 1.90 2006/06/15 15:37:55 ncq
3728 # - properly handle DOB in new-patient wizard
3729 #
3730 # Revision 1.89 2006/06/12 18:31:31 ncq
3731 # - must create *patient* not person from new patient wizard
3732 # if to be activated as patient :-)
3733 #
3734 # Revision 1.88 2006/06/09 14:40:24 ncq
3735 # - use fuzzy.timestamp for create_identity()
3736 #
3737 # Revision 1.87 2006/06/05 21:33:03 ncq
3738 # - Sebastian is too good at finding bugs, so fix them:
3739 # - proper queries for new-patient wizard phrasewheels
3740 # - properly validate timestamps
3741 #
3742 # Revision 1.86 2006/06/04 22:23:03 ncq
3743 # - consistently use l10n_country
3744 #
3745 # Revision 1.85 2006/06/04 21:38:49 ncq
3746 # - make state red as it's mandatory
3747 #
3748 # Revision 1.84 2006/06/04 21:31:44 ncq
3749 # - allow characters in phone URL
3750 #
3751 # Revision 1.83 2006/06/04 21:16:27 ncq
3752 # - fix missing dem. prefixes
3753 #
3754 # Revision 1.82 2006/05/28 20:49:44 ncq
3755 # - gmDateInput -> cFuzzyTimestampInput
3756 #
3757 # Revision 1.81 2006/05/15 13:35:59 ncq
3758 # - signal cleanup:
3759 # - activating_patient -> pre_patient_selection
3760 # - patient_selected -> post_patient_selection
3761 #
3762 # Revision 1.80 2006/05/14 21:44:22 ncq
3763 # - add get_workplace() to gmPerson.gmCurrentProvider and make use thereof
3764 # - remove use of gmWhoAmI.py
3765 #
3766 # Revision 1.79 2006/05/12 12:18:11 ncq
3767 # - whoami -> whereami cleanup
3768 # - use gmCurrentProvider()
3769 #
3770 # Revision 1.78 2006/05/04 09:49:20 ncq
3771 # - get_clinical_record() -> get_emr()
3772 # - adjust to changes in set_active_patient()
3773 # - need explicit set_active_patient() after ask_for_patient() if wanted
3774 #
3775 # Revision 1.77 2006/01/18 14:14:39 sjtan
3776 #
3777 # make reusable
3778 #
3779 # Revision 1.76 2006/01/10 14:22:24 sjtan
3780 #
3781 # movement to schema dem
3782 #
3783 # Revision 1.75 2006/01/09 10:46:18 ncq
3784 # - yet more schema quals
3785 #
3786 # Revision 1.74 2006/01/07 17:52:38 ncq
3787 # - several schema qualifications
3788 #
3789 # Revision 1.73 2005/10/19 09:12:40 ncq
3790 # - cleanup
3791 #
3792 # Revision 1.72 2005/10/09 08:10:22 ihaywood
3793 # ok, re-order the address widgets "the hard way" so tab-traversal works correctly.
3794 #
3795 # minor bugfixes so saving address actually works now
3796 #
3797 # Revision 1.71 2005/10/09 02:19:40 ihaywood
3798 # the address widget now has the appropriate widget order and behaviour for australia
3799 # when os.environ["LANG"] == 'en_AU' (is their a more graceful way of doing this?)
3800 #
3801 # Remember our postcodes work very differently.
3802 #
3803 # Revision 1.70 2005/09/28 21:27:30 ncq
3804 # - a lot of wx2.6-ification
3805 #
3806 # Revision 1.69 2005/09/28 19:47:01 ncq
3807 # - runs until login dialog
3808 #
3809 # Revision 1.68 2005/09/28 15:57:48 ncq
3810 # - a whole bunch of wx.Foo -> wx.Foo
3811 #
3812 # Revision 1.67 2005/09/27 20:44:58 ncq
3813 # - wx.wx* -> wx.*
3814 #
3815 # Revision 1.66 2005/09/26 18:01:50 ncq
3816 # - use proper way to import wx26 vs wx2.4
3817 # - note: THIS WILL BREAK RUNNING THE CLIENT IN SOME PLACES
3818 # - time for fixup
3819 #
3820 # Revision 1.65 2005/09/25 17:30:58 ncq
3821 # - revert back to wx2.4 style import awaiting "proper" wx2.6 importing
3822 #
3823 # Revision 1.64 2005/09/25 01:00:47 ihaywood
3824 # bugfixes
3825 #
3826 # remember 2.6 uses "import wx" not "from wxPython import wx"
3827 # removed not null constraint on clin_encounter.rfe as has no value on instantiation
3828 # client doesn't try to set clin_encounter.description as it doesn't exist anymore
3829 #
3830 # Revision 1.63 2005/09/24 09:17:27 ncq
3831 # - some wx2.6 compatibility fixes
3832 #
3833 # Revision 1.62 2005/09/12 15:09:00 ncq
3834 # - make first tab display first in demographics editor
3835 #
3836 # Revision 1.61 2005/09/04 07:29:53 ncq
3837 # - allow phrasewheeling states by abbreviation in new-patient wizard
3838 #
3839 # Revision 1.60 2005/08/14 15:36:54 ncq
3840 # - fix phrasewheel queries for country matching
3841 #
3842 # Revision 1.59 2005/08/08 08:08:35 ncq
3843 # - cleanup
3844 #
3845 # Revision 1.58 2005/07/31 14:48:44 ncq
3846 # - catch exceptions in TransferToWindow
3847 #
3848 # Revision 1.57 2005/07/24 18:54:18 ncq
3849 # - cleanup
3850 #
3851 # Revision 1.56 2005/07/04 11:26:50 ncq
3852 # - re-enable auto-setting gender from firstname, and speed it up, too
3853 #
3854 # Revision 1.55 2005/07/02 18:20:22 ncq
3855 # - allow English input of country as well, regardless of locale
3856 #
3857 # Revision 1.54 2005/06/29 15:03:32 ncq
3858 # - some cleanup
3859 #
3860 # Revision 1.53 2005/06/28 14:38:21 cfmoro
3861 # Integration fixes
3862 #
3863 # Revision 1.52 2005/06/28 14:12:55 cfmoro
3864 # Integration in space fixes
3865 #
3866 # Revision 1.51 2005/06/28 13:11:05 cfmoro
3867 # Fixed bug: when updating patient details the dob was converted from date to str type
3868 #
3869 # Revision 1.50 2005/06/14 19:51:27 cfmoro
3870 # auto zip in patient wizard and minor cleanups
3871 #
3872 # Revision 1.49 2005/06/14 00:34:14 cfmoro
3873 # Matcher provider queries revisited
3874 #
3875 # Revision 1.48 2005/06/13 01:18:24 cfmoro
3876 # Improved input system support by zip, country
3877 #
3878 # Revision 1.47 2005/06/12 22:12:35 ncq
3879 # - prepare for staged (constrained) queries in demographics
3880 #
3881 # Revision 1.46 2005/06/10 23:22:43 ncq
3882 # - SQL2 match provider now requires query *list*
3883 #
3884 # Revision 1.45 2005/06/09 01:56:41 cfmoro
3885 # Initial code on zip -> (auto) address
3886 #
3887 # Revision 1.44 2005/06/09 00:26:07 cfmoro
3888 # PhraseWheels in patient editor. Tons of cleanups and validator fixes
3889 #
3890 # Revision 1.43 2005/06/08 22:03:02 cfmoro
3891 # Restored phrasewheel gender in wizard
3892 #
3893 # Revision 1.42 2005/06/08 01:25:42 cfmoro
3894 # PRW in wizards state and country. Validator fixes
3895 #
3896 # Revision 1.41 2005/06/04 10:17:51 ncq
3897 # - cleanup, cSmartCombo, some comments
3898 #
3899 # Revision 1.40 2005/06/03 15:50:38 cfmoro
3900 # State and country combos y patient edition
3901 #
3902 # Revision 1.39 2005/06/03 13:37:45 cfmoro
3903 # States and country combo selection. SmartCombo revamped. Passing country and state codes instead of names
3904 #
3905 # Revision 1.38 2005/06/03 00:56:19 cfmoro
3906 # Validate dob in patient wizard
3907 #
3908 # Revision 1.37 2005/06/03 00:37:33 cfmoro
3909 # Validate dob in patient identity page
3910 #
3911 # Revision 1.36 2005/06/03 00:01:41 cfmoro
3912 # Key fixes in new patient wizard
3913 #
3914 # Revision 1.35 2005/06/02 23:49:21 cfmoro
3915 # Gender use SmartCombo, several fixes
3916 #
3917 # Revision 1.34 2005/06/02 23:26:41 cfmoro
3918 # Name auto-selection in new patient wizard
3919 #
3920 # Revision 1.33 2005/06/02 12:17:25 cfmoro
3921 # Auto select gender according to firstname
3922 #
3923 # Revision 1.32 2005/05/28 12:18:01 cfmoro
3924 # Capitalize name, street, etc
3925 #
3926 # Revision 1.31 2005/05/28 12:00:53 cfmoro
3927 # Trigger FIXME to reflect changes in v_basic_person
3928 #
3929 # Revision 1.30 2005/05/28 11:45:19 cfmoro
3930 # Retrieve names from identity cache, so refreshing will be reflected
3931 #
3932 # Revision 1.29 2005/05/25 23:03:02 cfmoro
3933 # Minor fixes
3934 #
3935 # Revision 1.28 2005/05/24 19:57:14 ncq
3936 # - cleanup
3937 # - make cNotebookedPatEditionPanel a gmRegetMixin child instead of cPatEditionNotebook
3938 #
3939 # Revision 1.27 2005/05/23 12:01:08 cfmoro
3940 # Create/update comms
3941 #
3942 # Revision 1.26 2005/05/23 11:16:18 cfmoro
3943 # More cleanups and test functional fixes
3944 #
3945 # Revision 1.25 2005/05/23 09:20:37 cfmoro
3946 # More cleaning up
3947 #
3948 # Revision 1.24 2005/05/22 22:12:06 ncq
3949 # - cleaning up patient edition notebook
3950 #
3951 # Revision 1.23 2005/05/19 16:06:50 ncq
3952 # - just silly cleanup, as usual
3953 #
3954 # Revision 1.22 2005/05/19 15:25:53 cfmoro
3955 # Initial logic to update patient details. Needs fixing.
3956 #
3957 # Revision 1.21 2005/05/17 15:09:28 cfmoro
3958 # Reloading values from backend in repopulate to properly reflect patient activated
3959 #
3960 # Revision 1.20 2005/05/17 14:56:02 cfmoro
3961 # Restore values from model to window action function
3962 #
3963 # Revision 1.19 2005/05/17 14:41:36 cfmoro
3964 # Notebooked patient editor initial code
3965 #
3966 # Revision 1.18 2005/05/17 08:04:28 ncq
3967 # - some cleanup
3968 #
3969 # Revision 1.17 2005/05/14 14:56:41 ncq
3970 # - add Carlos' DTD code
3971 # - numerous fixes/robustification
3972 # move occupation down based on user feedback
3973 #
3974 # Revision 1.16 2005/05/05 06:25:56 ncq
3975 # - cleanup, remove _() in log statements
3976 # - re-ordering in new patient wizard due to user feedback
3977 # - add <activate> to RunWizard(): if true activate patient after creation
3978 #
3979 # Revision 1.15 2005/04/30 20:31:03 ncq
3980 # - first-/lastname were switched around when saving identity into backend
3981 #
3982 # Revision 1.14 2005/04/28 19:21:18 cfmoro
3983 # zip code streamlining
3984 #
3985 # Revision 1.13 2005/04/28 16:58:45 cfmoro
3986 # Removed fixme, was dued to log buffer
3987 #
3988 # Revision 1.12 2005/04/28 16:24:47 cfmoro
3989 # Remove last references to town zip code
3990 #
3991 # Revision 1.11 2005/04/28 16:21:17 cfmoro
3992 # Leave town zip code out and street zip code optional as in schema
3993 #
3994 # Revision 1.10 2005/04/25 21:22:17 ncq
3995 # - some cleanup
3996 # - make cNewPatientWizard inherit directly from wxWizard as it should IMO
3997 #
3998 # Revision 1.9 2005/04/25 16:59:11 cfmoro
3999 # Implemented patient creation. Added conditional validator
4000 #
4001 # Revision 1.8 2005/04/25 08:29:24 ncq
4002 # - combobox items must be strings
4003 #
4004 # Revision 1.7 2005/04/23 06:34:11 cfmoro
4005 # Added address number and street zip code missing fields
4006 #
4007 # Revision 1.6 2005/04/18 19:19:54 ncq
4008 # - wrong field order in some match providers
4009 #
4010 # Revision 1.5 2005/04/14 18:26:19 ncq
4011 # - turn gender input into phrase wheel with fixed list
4012 # - some cleanup
4013 #
4014 # Revision 1.4 2005/04/14 08:53:56 ncq
4015 # - cIdentity moved
4016 # - improved tooltips and phrasewheel thresholds
4017 #
4018 # Revision 1.3 2005/04/12 18:49:04 cfmoro
4019 # Added missing fields and matcher providers
4020 #
4021 # Revision 1.2 2005/04/12 16:18:00 ncq
4022 # - match firstnames against name_gender_map, too
4023 #
4024 # Revision 1.1 2005/04/11 18:09:55 ncq
4025 # - offers demographic widgets
4026 #
4027 # Revision 1.62 2005/04/11 18:03:32 ncq
4028 # - attach some match providers to first new-patient wizard page
4029 #
4030 # Revision 1.61 2005/04/10 12:09:17 cfmoro
4031 # GUI implementation of the first-basic (wizard) page for patient details input
4032 #
4033 # Revision 1.60 2005/03/20 17:49:45 ncq
4034 # - improve split window handling, cleanup
4035 #
4036 # Revision 1.59 2005/03/06 09:21:08 ihaywood
4037 # stole a couple of icons from Richard's demo code
4038 #
4039 # Revision 1.58 2005/03/06 08:17:02 ihaywood
4040 # forms: back to the old way, with support for LaTeX tables
4041 #
4042 # business objects now support generic linked tables, demographics
4043 # uses them to the same functionality as before (loading, no saving)
4044 # They may have no use outside of demographics, but saves much code already.
4045 #
4046 # Revision 1.57 2005/02/22 10:21:33 ihaywood
4047 # new patient
4048 #
4049 # Revision 1.56 2005/02/20 10:45:49 sjtan
4050 #
4051 # kwargs syntax error.
4052 #
4053 # Revision 1.55 2005/02/20 10:15:16 ihaywood
4054 # some tidying up
4055 #
4056 # Revision 1.54 2005/02/20 09:46:08 ihaywood
4057 # demographics module with load a patient with no exceptions
4058 #
4059 # Revision 1.53 2005/02/18 11:16:41 ihaywood
4060 # new demographics UI code won't crash the whole client now ;-)
4061 # still needs much work
4062 # RichardSpace working
4063 #
4064 # Revision 1.52 2005/02/03 20:19:16 ncq
4065 # - get_demographic_record() -> get_identity()
4066 #
4067 # Revision 1.51 2005/02/01 10:16:07 ihaywood
4068 # refactoring of gmDemographicRecord and follow-on changes as discussed.
4069 #
4070 # gmTopPanel moves to gmHorstSpace
4071 # gmRichardSpace added -- example code at present, haven't even run it myself
4072 # (waiting on some icon .pngs from Richard)
4073 #
4074 # Revision 1.50 2005/01/31 10:37:26 ncq
4075 # - gmPatient.py -> gmPerson.py
4076 #
4077 # Revision 1.49 2004/12/18 13:45:51 sjtan
4078 #
4079 # removed timer.
4080 #
4081 # Revision 1.48 2004/10/20 11:20:10 sjtan
4082 # restore imports.
4083 #
4084 # Revision 1.47 2004/10/19 21:34:25 sjtan
4085 # dir is direction, and this is checked
4086 #
4087 # Revision 1.46 2004/10/19 21:29:25 sjtan
4088 # remove division by zero problem, statement occurs later after check for non-zero.
4089 #
4090 # Revision 1.45 2004/10/17 23:49:21 sjtan
4091 #
4092 # the timer autoscroll idea.
4093 #
4094 # Revision 1.44 2004/10/17 22:26:42 sjtan
4095 #
4096 # split window new look Richard's demographics ( his eye for gui design is better
4097 # than most of ours). Rollback if vote no.
4098 #
4099 # Revision 1.43 2004/10/16 22:42:12 sjtan
4100 #
4101 # script for unitesting; guard for unit tests where unit uses gmPhraseWheel; fixup where version of wxPython doesn't allow
4102 # a child widget to be multiply inserted (gmDemographics) ; try block for later versions of wxWidgets that might fail
4103 # the Add (.. w,h, ... ) because expecting Add(.. (w,h) ...)
4104 #
4105 # Revision 1.42 2004/09/10 10:51:14 ncq
4106 # - improve previous checkin comment
4107 #
4108 # Revision 1.41 2004/09/10 10:41:38 ncq
4109 # - remove dead import
4110 # - lots of cleanup (whitespace, indention, style, local vars instead of instance globals)
4111 # - remove an extra sizer, waste less space
4112 # - translate strings
4113 # - from wxPython.wx import * -> from wxPython import wx
4114 # Why ? Because we can then do a simple replace wx. -> wx. for 2.5 code.
4115 #
4116 # Revision 1.40 2004/08/24 14:29:58 ncq
4117 # - some cleanup, not there yet, though
4118 #
4119 # Revision 1.39 2004/08/23 10:25:36 ncq
4120 # - Richards work, removed pat photo, store column sizes
4121 #
4122 # Revision 1.38 2004/08/20 13:34:48 ncq
4123 # - getFirstMatchingDBSet() -> getDBParam()
4124 #
4125 # Revision 1.37 2004/08/18 08:15:21 ncq
4126 # - check if column size for patient list is missing
4127 #
4128 # Revision 1.36 2004/08/16 13:32:19 ncq
4129 # - rework of GUI layout by R.Terry
4130 # - save patient list column width from right click popup menu
4131 #
4132 # Revision 1.35 2004/07/30 13:43:33 sjtan
4133 #
4134 # update import
4135 #
4136 # Revision 1.34 2004/07/26 12:04:44 sjtan
4137 #
4138 # character level immediate validation , as per Richard's suggestions.
4139 #
4140 # Revision 1.33 2004/07/20 01:01:46 ihaywood
4141 # changing a patients name works again.
4142 # Name searching has been changed to query on names rather than v_basic_person.
4143 # This is so the old (inactive) names are still visible to the search.
4144 # This is so when Mary Smith gets married, we can still find her under Smith.
4145 # [In Australia this odd tradition is still the norm, even female doctors
4146 # have their medical registration documents updated]
4147 #
4148 # SOAPTextCtrl now has popups, but the cursor vanishes (?)
4149 #
4150 # Revision 1.32 2004/07/18 20:30:53 ncq
4151 # - wxPython.true/false -> Python.True/False as Python tells us to do
4152 #
4153 # Revision 1.31 2004/06/30 15:09:47 shilbert
4154 # - more wxMAC fixes
4155 #
4156 # Revision 1.30 2004/06/29 22:48:47 shilbert
4157 # - one more wxMAC fix
4158 #
4159 # Revision 1.29 2004/06/27 13:42:26 ncq
4160 # - further Mac fixes - maybe 2.5 issues ?
4161 #
4162 # Revision 1.28 2004/06/23 21:26:28 ncq
4163 # - kill dead code, fixup for Mac
4164 #
4165 # Revision 1.27 2004/06/20 17:28:34 ncq
4166 # - The Great Butchering begins
4167 # - remove dead plugin code
4168 # - rescue binoculars xpm to artworks/
4169 #
4170 # Revision 1.26 2004/06/17 11:43:12 ihaywood
4171 # Some minor bugfixes.
4172 # My first experiments with wxGlade
4173 # changed gmPhraseWheel so the match provider can be added after instantiation
4174 # (as wxGlade can't do this itself)
4175 #
4176 # Revision 1.25 2004/06/13 22:31:48 ncq
4177 # - gb['main.toolbar'] -> gb['main.top_panel']
4178 # - self.internal_name() -> self.__class__.__name__
4179 # - remove set_widget_reference()
4180 # - cleanup
4181 # - fix lazy load in _on_patient_selected()
4182 # - fix lazy load in ReceiveFocus()
4183 # - use self._widget in self.GetWidget()
4184 # - override populate_with_data()
4185 # - use gb['main.notebook.raised_plugin']
4186 #
4187 # Revision 1.24 2004/05/27 13:40:22 ihaywood
4188 # more work on referrals, still not there yet
4189 #
4190 # Revision 1.23 2004/05/25 16:18:12 sjtan
4191 #
4192 # move methods for postcode -> urb interaction to gmDemographics so gmContacts can use it.
4193 #
4194 # Revision 1.22 2004/05/25 16:00:34 sjtan
4195 #
4196 # move common urb/postcode collaboration to business class.
4197 #
4198 # Revision 1.21 2004/05/23 11:13:59 sjtan
4199 #
4200 # some data fields not in self.input_fields , so exclude them
4201 #
4202 # Revision 1.20 2004/05/19 11:16:09 sjtan
4203 #
4204 # allow selecting the postcode for restricting the urb's picklist, and resetting
4205 # the postcode for unrestricting the urb picklist.
4206 #
4207 # Revision 1.19 2004/03/27 04:37:01 ihaywood
4208 # lnk_person2address now lnk_person_org_address
4209 # sundry bugfixes
4210 #
4211 # Revision 1.18 2004/03/25 11:03:23 ncq
4212 # - getActiveName -> get_names
4213 #
4214 # Revision 1.17 2004/03/15 15:43:17 ncq
4215 # - cleanup imports
4216 #
4217 # Revision 1.16 2004/03/09 07:34:51 ihaywood
4218 # reactivating plugins
4219 #
4220 # Revision 1.15 2004/03/04 11:19:05 ncq
4221 # - put a comment as to where to handle result from setCOB
4222 #
4223 # Revision 1.14 2004/03/03 23:53:22 ihaywood
4224 # GUI now supports external IDs,
4225 # Demographics GUI now ALPHA (feature-complete w.r.t. version 1.0)
4226 # but happy to consider cosmetic changes
4227 #
4228 # Revision 1.13 2004/03/03 05:24:01 ihaywood
4229 # patient photograph support
4230 #
4231 # Revision 1.12 2004/03/02 23:57:59 ihaywood
4232 # Support for full range of backend genders
4233 #
4234 # Revision 1.11 2004/03/02 10:21:10 ihaywood
4235 # gmDemographics now supports comm channels, occupation,
4236 # country of birth and martial status
4237 #
4238 # Revision 1.10 2004/02/25 09:46:21 ncq
4239 # - import from pycommon now, not python-common
4240 #
4241 # Revision 1.9 2004/02/18 06:30:30 ihaywood
4242 # Demographics editor now can delete addresses
4243 # Contacts back up on screen.
4244 #
4245 # Revision 1.8 2004/01/18 21:49:18 ncq
4246 # - comment out debugging code
4247 #
4248 # Revision 1.7 2004/01/04 09:33:32 ihaywood
4249 # minor bugfixes, can now create new patients, but doesn't update properly
4250 #
4251 # Revision 1.6 2003/11/22 14:47:24 ncq
4252 # - use addName instead of setActiveName
4253 #
4254 # Revision 1.5 2003/11/22 12:29:16 sjtan
4255 #
4256 # minor debugging; remove _newPatient flag attribute conflict with method name newPatient.
4257 #
4258 # Revision 1.4 2003/11/20 02:14:42 sjtan
4259 #
4260 # use global module function getPostcodeByUrbId() , and renamed MP_urb_by_zip.
4261 #
4262 # Revision 1.3 2003/11/19 23:11:58 sjtan
4263 #
4264 # using local time tuple conversion function; mxDateTime object sometimes can't convert to int.
4265 # Changed to global module.getAddressTypes(). To decide: mechanism for postcode update when
4266 # suburb selected ( not back via gmDemographicRecord.getPostcodeForUrbId(), ? via linked PhraseWheel matchers ?)
4267 #
4268 # Revision 1.2 2003/11/18 16:46:02 ncq
4269 # - sync with method name changes
4270 #
4271 # Revision 1.1 2003/11/17 11:04:34 sjtan
4272 #
4273 # added.
4274 #
4275 # Revision 1.1 2003/10/23 06:02:40 sjtan
4276 #
4277 # manual edit areas modelled after r.terry's specs.
4278 #
4279 # Revision 1.26 2003/04/28 12:14:40 ncq
4280 # - use .internal_name()
4281 #
4282 # Revision 1.25 2003/04/25 11:15:58 ncq
4283 # cleanup
4284 #
4285 # Revision 1.24 2003/04/05 00:39:23 ncq
4286 # - "patient" is now "clinical", changed all the references
4287 #
4288 # Revision 1.23 2003/04/04 20:52:44 ncq
4289 # - start disentanglement with top pane:
4290 # - remove patient search/age/allergies/patient details
4291 #
4292 # Revision 1.22 2003/03/29 18:27:14 ncq
4293 # - make age/allergies read-only, cleanup
4294 #
4295 # Revision 1.21 2003/03/29 13:50:09 ncq
4296 # - adapt to new "top row" panel
4297 #
4298 # Revision 1.20 2003/03/28 16:43:12 ncq
4299 # - some cleanup in preparation of inserting the patient searcher
4300 #
4301 # Revision 1.19 2003/02/09 23:42:50 ncq
4302 # - date time conversion to age string does not work, set to 20 for now, fix soon
4303 #
4304 # Revision 1.18 2003/02/09 12:05:02 sjtan
4305 #
4306 #
4307 # wx.BasePlugin is unnecessarily specific.
4308 #
4309 # Revision 1.17 2003/02/09 11:57:42 ncq
4310 # - cleanup, cvs keywords
4311 #
4312 # old change log:
4313 # 10.06.2002 rterry initial implementation, untested
4314 # 30.07.2002 rterry images put in file
4315
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Feb 9 04:02:21 2010 | http://epydoc.sourceforge.net |