Package Gnumed :: Package wxpython :: Module gmDemographicsWidgets
[frames] | no frames]

Source Code for Module Gnumed.wxpython.gmDemographicsWidgets

   1  """Widgets dealing with patient demographics.""" 
   2  #============================================================ 
   3  __version__ = "$Revision: 1.175 $" 
   4  __author__ = "R.Terry, SJ Tan, I Haywood, Carlos Moro <cfmoro1976@yahoo.es>" 
   5  __license__ = 'GPL (details at http://www.gnu.org)' 
   6   
   7  # standard library 
   8  import time, string, sys, os, datetime as pyDT, csv, codecs, re as regex, psycopg2, logging 
   9   
  10   
  11  import wx 
  12  import wx.wizard 
  13   
  14   
  15  # GNUmed specific 
  16  if __name__ == '__main__': 
  17          sys.path.insert(0, '../../') 
  18  from Gnumed.pycommon import gmDispatcher, gmI18N, gmMatchProvider, gmPG2, gmTools, gmCfg 
  19  from Gnumed.pycommon import gmDateTime, gmShellAPI 
  20  from Gnumed.business import gmDemographicRecord, gmPerson, gmSurgery 
  21  from Gnumed.wxpython import gmPlugin, gmPhraseWheel, gmGuiHelpers, gmDateTimeInput 
  22  from Gnumed.wxpython import gmRegetMixin, gmDataMiningWidgets, gmListWidgets, gmEditArea 
  23  from Gnumed.wxpython import gmAuthWidgets, gmCfgWidgets 
  24  from Gnumed.wxGladeWidgets import wxgGenericAddressEditAreaPnl, wxgPersonContactsManagerPnl, wxgPersonIdentityManagerPnl 
  25  from Gnumed.wxGladeWidgets import wxgCommChannelEditAreaPnl, wxgExternalIDEditAreaPnl 
  26   
  27   
  28  # constant defs 
  29  _log = logging.getLogger('gm.ui') 
  30   
  31   
  32  try: 
  33          _('dummy-no-need-to-translate-but-make-epydoc-happy') 
  34  except NameError: 
  35          _ = lambda x:x 
  36   
  37  #============================================================ 
  38  # country related widgets / functions 
  39  #============================================================ 
40 -def configure_default_country(parent=None):
41 42 if parent is None: 43 parent = wx.GetApp().GetTopWindow() 44 45 countries = gmDemographicRecord.get_countries() 46 47 gmCfgWidgets.configure_string_from_list_option ( 48 parent = parent, 49 message = _('Select the default country for new persons.\n'), 50 option = 'person.create.default_country', 51 bias = 'user', 52 choices = [ (c['l10n_country'], c['code']) for c in countries ], 53 columns = [_('Country'), _('Code')], 54 data = [ c['name'] for c in countries ] 55 )
56 #============================================================
57 -class cCountryPhraseWheel(gmPhraseWheel.cPhraseWheel):
58
59 - def __init__(self, *args, **kwargs):
60 61 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 62 63 context = { 64 u'ctxt_zip': { 65 u'where_part': u'and zip ilike %(zip)s', 66 u'placeholder': u'zip' 67 } 68 } 69 query = u""" 70 select code, name from ( 71 select distinct on (code, name) code, (name || ' (' || code || ')') as name, rank from ( 72 73 -- localized to user 74 75 select 76 code_country as code, l10n_country as name, 1 as rank 77 from dem.v_zip2data 78 where 79 l10n_country %(fragment_condition)s 80 %(ctxt_zip)s 81 82 union all 83 84 select 85 code as code, _(name) as name, 2 as rank 86 from dem.country 87 where 88 _(name) %(fragment_condition)s 89 90 union all 91 92 -- non-localized 93 94 select 95 code_country as code, country as name, 3 as rank 96 from dem.v_zip2data 97 where 98 country %(fragment_condition)s 99 %(ctxt_zip)s 100 101 union all 102 103 select 104 code as code, name as name, 4 as rank 105 from dem.country 106 where 107 name %(fragment_condition)s 108 109 union all 110 111 -- abbreviation 112 113 select 114 code as code, name as name, 5 as rank 115 from dem.country 116 where 117 code %(fragment_condition)s 118 119 ) as q2 120 ) as q1 order by rank, name limit 25""" 121 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context) 122 mp.setThresholds(2, 5, 9) 123 self.matcher = mp 124 125 self.unset_context(context = u'zip') 126 self.SetToolTipString(_('Type or select a country.')) 127 self.capitalisation_mode = gmTools.CAPS_FIRST 128 self.selection_only = True
129 130 #============================================================ 131 # province related widgets / functions 132 #============================================================
133 -def configure_default_region(parent=None):
134 135 if parent is None: 136 parent = wx.GetApp().GetTopWindow() 137 138 provs = gmDemographicRecord.get_provinces() 139 140 gmCfgWidgets.configure_string_from_list_option ( 141 parent = parent, 142 message = _('Select the default region/province/state/territory for new persons.\n'), 143 option = 'person.create.default_region', 144 bias = 'user', 145 choices = [ (p['l10n_country'], p['l10n_state'], p['code_state']) for p in provs ], 146 columns = [_('Country'), _('Region'), _('Code')], 147 data = [ p['state'] for p in provs ] 148 )
149 #============================================================
150 -def edit_province(parent=None, province=None):
151 ea = cProvinceEAPnl(parent = parent, id = -1, province = province) 152 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = (province is not None)) 153 dlg.SetTitle(gmTools.coalesce(province, _('Adding province'), _('Editing province'))) 154 result = dlg.ShowModal() 155 dlg.Destroy() 156 return (result == wx.ID_OK)
157 #============================================================
158 -def delete_province(parent=None, province=None):
159 160 msg = _( 161 'Are you sure you want to delete this province ?\n' 162 '\n' 163 'Deletion will only work if this province is not\n' 164 'yet in use in any patient addresses.' 165 ) 166 167 tt = _( 168 'Also delete any towns/cities/villages known\n' 169 'to be situated in this state as long as\n' 170 'no patients are recorded to live there.' 171 ) 172 173 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 174 parent, 175 -1, 176 caption = _('Deleting province'), 177 question = msg, 178 show_checkbox = True, 179 checkbox_msg = _('delete related townships'), 180 checkbox_tooltip = tt, 181 button_defs = [ 182 {'label': _('Yes, delete'), 'tooltip': _('Delete province and possibly related townships.'), 'default': False}, 183 {'label': _('No'), 'tooltip': _('No, do NOT delete anything.'), 'default': True} 184 ] 185 ) 186 187 decision = dlg.ShowModal() 188 if decision != wx.ID_YES: 189 dlg.Destroy() 190 return False 191 192 include_urbs = dlg.checkbox_is_checked() 193 dlg.Destroy() 194 195 return gmDemographicRecord.delete_province(province = province, delete_urbs = include_urbs)
196 #============================================================
197 -def manage_provinces(parent=None):
198 199 if parent is None: 200 parent = wx.GetApp().GetTopWindow() 201 202 #------------------------------------------------------------ 203 def delete(province=None): 204 return delete_province(parent = parent, province = province['pk_state'])
205 #------------------------------------------------------------ 206 def edit(province=None): 207 return edit_province(parent = parent, province = province) 208 #------------------------------------------------------------ 209 def refresh(lctrl): 210 wx.BeginBusyCursor() 211 provinces = gmDemographicRecord.get_provinces() 212 lctrl.set_string_items([ (p['l10n_country'], p['l10n_state']) for p in provinces ]) 213 lctrl.set_data(provinces) 214 wx.EndBusyCursor() 215 #------------------------------------------------------------ 216 msg = _( 217 '\n' 218 'This list shows the provinces known to GNUmed.\n' 219 '\n' 220 'In your jurisdiction "province" may correspond to either of "state",\n' 221 '"county", "region", "territory", or some such term.\n' 222 '\n' 223 'Select the province you want to edit !\n' 224 ) 225 226 gmListWidgets.get_choices_from_list ( 227 parent = parent, 228 msg = msg, 229 caption = _('Editing provinces ...'), 230 columns = [_('Country'), _('Province')], 231 single_selection = True, 232 new_callback = edit, 233 #edit_callback = edit, 234 delete_callback = delete, 235 refresh_callback = refresh 236 ) 237 #============================================================
238 -class cStateSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
239
240 - def __init__(self, *args, **kwargs):
241 242 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 243 244 context = { 245 u'ctxt_country_name': { 246 u'where_part': u'and l10n_country ilike %(country_name)s or country ilike %(country_name)s', 247 u'placeholder': u'country_name' 248 }, 249 u'ctxt_zip': { 250 u'where_part': u'and zip ilike %(zip)s', 251 u'placeholder': u'zip' 252 }, 253 u'ctxt_country_code': { 254 u'where_part': u'and country in (select code from dem.country where _(name) ilike %(country_name)s or name ilike %(country_name)s)', 255 u'placeholder': u'country_name' 256 } 257 } 258 259 query = u""" 260 select code, name from ( 261 select distinct on (name) code, name, rank from ( 262 -- 1: find states based on name, context: zip and country name 263 select 264 code_state as code, state as name, 1 as rank 265 from dem.v_zip2data 266 where 267 state %(fragment_condition)s 268 %(ctxt_country_name)s 269 %(ctxt_zip)s 270 271 union all 272 273 -- 2: find states based on code, context: zip and country name 274 select 275 code_state as code, state as name, 2 as rank 276 from dem.v_zip2data 277 where 278 code_state %(fragment_condition)s 279 %(ctxt_country_name)s 280 %(ctxt_zip)s 281 282 union all 283 284 -- 3: find states based on name, context: country 285 select 286 code as code, name as name, 3 as rank 287 from dem.state 288 where 289 name %(fragment_condition)s 290 %(ctxt_country_code)s 291 292 union all 293 294 -- 4: find states based on code, context: country 295 select 296 code as code, name as name, 3 as rank 297 from dem.state 298 where 299 code %(fragment_condition)s 300 %(ctxt_country_code)s 301 302 ) as q2 303 ) as q1 order by rank, name limit 50""" 304 305 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context) 306 mp.setThresholds(2, 5, 6) 307 mp.word_separators = u'[ \t]+' 308 self.matcher = mp 309 310 self.unset_context(context = u'zip') 311 self.unset_context(context = u'country_name') 312 self.SetToolTipString(_('Type or select a state/region/province/territory.')) 313 self.capitalisation_mode = gmTools.CAPS_FIRST 314 self.selection_only = True
315 #==================================================================== 316 from Gnumed.wxGladeWidgets import wxgProvinceEAPnl 317
318 -class cProvinceEAPnl(wxgProvinceEAPnl.wxgProvinceEAPnl, gmEditArea.cGenericEditAreaMixin):
319
320 - def __init__(self, *args, **kwargs):
321 322 try: 323 data = kwargs['province'] 324 del kwargs['province'] 325 except KeyError: 326 data = None 327 328 wxgProvinceEAPnl.wxgProvinceEAPnl.__init__(self, *args, **kwargs) 329 gmEditArea.cGenericEditAreaMixin.__init__(self) 330 331 self.mode = 'new' 332 self.data = data 333 if data is not None: 334 self.mode = 'edit' 335 336 self.__init_ui()
337 #----------------------------------------------------------------
338 - def __init_ui(self):
339 self._PRW_province.selection_only = False
340 #---------------------------------------------------------------- 341 # generic Edit Area mixin API 342 #----------------------------------------------------------------
343 - def _valid_for_save(self):
344 345 validity = True 346 347 if self._PRW_province.GetData() is None: 348 if self._PRW_province.GetValue().strip() == u'': 349 validity = False 350 self._PRW_province.display_as_valid(False) 351 else: 352 self._PRW_province.display_as_valid(True) 353 else: 354 self._PRW_province.display_as_valid(True) 355 356 if self._PRW_province.GetData() is None: 357 if self._TCTRL_code.GetValue().strip() == u'': 358 validity = False 359 self._TCTRL_code.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 360 else: 361 self._TCTRL_code.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 362 363 if self._PRW_country.GetData() is None: 364 validity = False 365 self._PRW_country.display_as_valid(False) 366 else: 367 self._PRW_country.display_as_valid(True) 368 369 return validity
370 #----------------------------------------------------------------
371 - def _save_as_new(self):
372 gmDemographicRecord.create_province ( 373 name = self._PRW_province.GetValue().strip(), 374 code = self._TCTRL_code.GetValue().strip(), 375 country = self._PRW_country.GetData() 376 ) 377 378 # EA is refreshed automatically after save, so need this ... 379 self.data = { 380 'l10n_state' : self._PRW_province.GetValue().strip(), 381 'code_state' : self._TCTRL_code.GetValue().strip(), 382 'l10n_country' : self._PRW_country.GetValue().strip() 383 } 384 385 return True
386 #----------------------------------------------------------------
387 - def _save_as_update(self):
388 # update self.data and save the changes 389 #self.data[''] = 390 #self.data[''] = 391 #self.data[''] = 392 #self.data.save() 393 394 # do nothing for now (IOW, don't support updates) 395 return True
396 #----------------------------------------------------------------
397 - def _refresh_as_new(self):
398 self._PRW_province.SetText() 399 self._TCTRL_code.SetValue(u'') 400 self._PRW_country.SetText() 401 402 self._PRW_province.SetFocus()
403 #----------------------------------------------------------------
404 - def _refresh_from_existing(self):
405 self._PRW_province.SetText(self.data['l10n_state'], self.data['code_state']) 406 self._TCTRL_code.SetValue(self.data['code_state']) 407 self._PRW_country.SetText(self.data['l10n_country'], self.data['code_country']) 408 409 self._PRW_province.SetFocus()
410 #----------------------------------------------------------------
412 self._PRW_province.SetText() 413 self._TCTRL_code.SetValue(u'') 414 self._PRW_country.SetText(self.data['l10n_country'], self.data['code_country']) 415 416 self._PRW_province.SetFocus()
417 #============================================================ 418 #============================================================
419 -class cKOrganizerSchedulePnl(gmDataMiningWidgets.cPatientListingPnl):
420
421 - def __init__(self, *args, **kwargs):
422 423 kwargs['message'] = _("Today's KOrganizer appointments ...") 424 kwargs['button_defs'] = [ 425 {'label': _('Reload'), 'tooltip': _('Reload appointments from KOrganizer')}, 426 {'label': u''}, 427 {'label': u''}, 428 {'label': u''}, 429 {'label': u'KOrganizer', 'tooltip': _('Launch KOrganizer')} 430 ] 431 gmDataMiningWidgets.cPatientListingPnl.__init__(self, *args, **kwargs) 432 433 self.fname = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp', 'korganizer2gnumed.csv')) 434 self.reload_cmd = 'konsolekalendar --view --export-type csv --export-file %s' % self.fname
435 436 #--------------------------------------------------------
437 - def _on_BTN_1_pressed(self, event):
438 """Reload appointments from KOrganizer.""" 439 self.reload_appointments()
440 #--------------------------------------------------------
441 - def _on_BTN_5_pressed(self, event):
442 """Reload appointments from KOrganizer.""" 443 found, cmd = gmShellAPI.detect_external_binary(binary = 'korganizer') 444 445 if not found: 446 gmDispatcher.send(signal = 'statustext', msg = _('KOrganizer is not installed.'), beep = True) 447 return 448 449 gmShellAPI.run_command_in_shell(command = cmd, blocking = False)
450 #--------------------------------------------------------
451 - def reload_appointments(self):
452 try: os.remove(self.fname) 453 except OSError: pass 454 gmShellAPI.run_command_in_shell(command=self.reload_cmd, blocking=True) 455 try: 456 csv_file = codecs.open(self.fname , mode = 'rU', encoding = 'utf8', errors = 'replace') 457 except IOError: 458 gmDispatcher.send(signal = u'statustext', msg = _('Cannot access KOrganizer transfer file [%s]') % self.fname, beep = True) 459 return 460 461 csv_lines = gmTools.unicode_csv_reader ( 462 csv_file, 463 delimiter = ',' 464 ) 465 # start_date, start_time, end_date, end_time, title (patient), ort, comment, UID 466 self._LCTRL_items.set_columns ([ 467 _('Place'), 468 _('Start'), 469 u'', 470 u'', 471 _('Patient'), 472 _('Comment') 473 ]) 474 items = [] 475 data = [] 476 for line in csv_lines: 477 items.append([line[5], line[0], line[1], line[3], line[4], line[6]]) 478 data.append([line[4], line[7]]) 479 480 self._LCTRL_items.set_string_items(items = items) 481 self._LCTRL_items.set_column_widths() 482 self._LCTRL_items.set_data(data = data) 483 self._LCTRL_items.patient_key = 0
484 #-------------------------------------------------------- 485 # notebook plugins API 486 #--------------------------------------------------------
487 - def repopulate_ui(self):
488 self.reload_appointments()
489 #============================================================
490 -def edit_occupation():
491 492 pat = gmPerson.gmCurrentPatient() 493 curr_jobs = pat.get_occupations() 494 if len(curr_jobs) > 0: 495 old_job = curr_jobs[0]['l10n_occupation'] 496 update = curr_jobs[0]['modified_when'].strftime('%m/%Y') 497 else: 498 old_job = u'' 499 update = u'' 500 501 msg = _( 502 'Please enter the primary occupation of the patient.\n' 503 '\n' 504 'Currently recorded:\n' 505 '\n' 506 ' %s (last updated %s)' 507 ) % (old_job, update) 508 509 new_job = wx.GetTextFromUser ( 510 message = msg, 511 caption = _('Editing primary occupation'), 512 default_value = old_job, 513 parent = None 514 ) 515 if new_job.strip() == u'': 516 return 517 518 for job in curr_jobs: 519 # unlink all but the new job 520 if job['l10n_occupation'] != new_job: 521 pat.unlink_occupation(occupation = job['l10n_occupation']) 522 # and link the new one 523 pat.link_occupation(occupation = new_job)
524 #============================================================
525 -def disable_identity(identity=None):
526 # ask user for assurance 527 go_ahead = gmGuiHelpers.gm_show_question ( 528 _('Are you sure you really, positively want\n' 529 'to disable the following person ?\n' 530 '\n' 531 ' %s %s %s\n' 532 ' born %s\n' 533 '\n' 534 '%s\n' 535 ) % ( 536 identity['firstnames'], 537 identity['lastnames'], 538 identity['gender'], 539 identity['dob'], 540 gmTools.bool2subst ( 541 identity.is_patient, 542 _('This patient DID receive care.'), 543 _('This person did NOT receive care.') 544 ) 545 ), 546 _('Disabling person') 547 ) 548 if not go_ahead: 549 return True 550 551 # get admin connection 552 conn = gmAuthWidgets.get_dbowner_connection ( 553 procedure = _('Disabling patient') 554 ) 555 # - user cancelled 556 if conn is False: 557 return True 558 # - error 559 if conn is None: 560 return False 561 562 # now disable patient 563 gmPG2.run_rw_queries(queries = [{'cmd': u"update dem.identity set deleted=True where pk=%s", 'args': [identity['pk_identity']]}]) 564 565 return True
566 #============================================================ 567 # address phrasewheels and widgets 568 #============================================================
569 -class cPersonAddressesManagerPnl(gmListWidgets.cGenericListManagerPnl):
570 """A list for managing a person's addresses. 571 572 Does NOT act on/listen to the current patient. 573 """
574 - def __init__(self, *args, **kwargs):
575 576 try: 577 self.__identity = kwargs['identity'] 578 del kwargs['identity'] 579 except KeyError: 580 self.__identity = None 581 582 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 583 584 self.new_callback = self._add_address 585 self.edit_callback = self._edit_address 586 self.delete_callback = self._del_address 587 self.refresh_callback = self.refresh 588 589 self.__init_ui() 590 self.refresh()
591 #-------------------------------------------------------- 592 # external API 593 #--------------------------------------------------------
594 - def refresh(self, *args, **kwargs):
595 if self.__identity is None: 596 self._LCTRL_items.set_string_items() 597 return 598 599 adrs = self.__identity.get_addresses() 600 self._LCTRL_items.set_string_items ( 601 items = [ [ 602 a['l10n_address_type'], 603 a['street'], 604 gmTools.coalesce(a['notes_street'], u''), 605 a['number'], 606 gmTools.coalesce(a['subunit'], u''), 607 a['postcode'], 608 a['urb'], 609 gmTools.coalesce(a['suburb'], u''), 610 a['l10n_state'], 611 a['l10n_country'], 612 gmTools.coalesce(a['notes_subunit'], u'') 613 ] for a in adrs 614 ] 615 ) 616 self._LCTRL_items.set_column_widths() 617 self._LCTRL_items.set_data(data = adrs)
618 #-------------------------------------------------------- 619 # internal helpers 620 #--------------------------------------------------------
621 - def __init_ui(self):
622 self._LCTRL_items.SetToolTipString(_('List of known addresses.')) 623 self._LCTRL_items.set_columns(columns = [ 624 _('Type'), 625 _('Street'), 626 _('Street info'), 627 _('Number'), 628 _('Subunit'), 629 _('Postal code'), 630 _('Place'), 631 _('Suburb'), 632 _('Region'), 633 _('Country'), 634 _('Comment') 635 ])
636 #--------------------------------------------------------
637 - def _add_address(self):
638 ea = cAddressEditAreaPnl(self, -1) 639 ea.identity = self.__identity 640 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 641 dlg.SetTitle(_('Adding new address')) 642 if dlg.ShowModal() == wx.ID_OK: 643 return True 644 return False
645 #--------------------------------------------------------
646 - def _edit_address(self, address):
647 ea = cAddressEditAreaPnl(self, -1, address = address) 648 ea.identity = self.__identity 649 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 650 dlg.SetTitle(_('Editing address')) 651 if dlg.ShowModal() == wx.ID_OK: 652 # did we add an entirely new address ? 653 # if so then unlink the old one as implied by "edit" 654 if ea.address['pk_address'] != address['pk_address']: 655 self.__identity.unlink_address(address = address) 656 return True 657 return False
658 #--------------------------------------------------------
659 - def _del_address(self, address):
660 go_ahead = gmGuiHelpers.gm_show_question ( 661 _( 'Are you sure you want to remove this\n' 662 "address from the patient's addresses ?\n" 663 '\n' 664 'The address itself will not be deleted\n' 665 'but it will no longer be associated with\n' 666 'this patient.' 667 ), 668 _('Removing address') 669 ) 670 if not go_ahead: 671 return False 672 self.__identity.unlink_address(address = address) 673 return True
674 #-------------------------------------------------------- 675 # properties 676 #--------------------------------------------------------
677 - def _get_identity(self):
678 return self.__identity
679
680 - def _set_identity(self, identity):
681 self.__identity = identity 682 self.refresh()
683 684 identity = property(_get_identity, _set_identity)
685 #============================================================
686 -class cPersonContactsManagerPnl(wxgPersonContactsManagerPnl.wxgPersonContactsManagerPnl):
687 """A panel for editing contact data for a person. 688 689 - provides access to: 690 - addresses 691 - communication paths 692 693 Does NOT act on/listen to the current patient. 694 """
695 - def __init__(self, *args, **kwargs):
696 697 wxgPersonContactsManagerPnl.wxgPersonContactsManagerPnl.__init__(self, *args, **kwargs) 698 699 self.__identity = None 700 self.refresh()
701 #-------------------------------------------------------- 702 # external API 703 #--------------------------------------------------------
704 - def refresh(self):
705 self._PNL_addresses.identity = self.__identity 706 self._PNL_comms.identity = self.__identity
707 #-------------------------------------------------------- 708 # properties 709 #--------------------------------------------------------
710 - def _get_identity(self):
711 return self.__identity
712
713 - def _set_identity(self, identity):
714 self.__identity = identity 715 self.refresh()
716 717 identity = property(_get_identity, _set_identity)
718 #============================================================
719 -class cAddressEditAreaPnl(wxgGenericAddressEditAreaPnl.wxgGenericAddressEditAreaPnl):
720 """An edit area for editing/creating an address. 721 722 Does NOT act on/listen to the current patient. 723 """
724 - def __init__(self, *args, **kwargs):
725 try: 726 self.address = kwargs['address'] 727 del kwargs['address'] 728 except KeyError: 729 self.address = None 730 731 wxgGenericAddressEditAreaPnl.wxgGenericAddressEditAreaPnl.__init__(self, *args, **kwargs) 732 733 self.identity = None 734 735 self.__register_interests() 736 self.refresh()
737 #-------------------------------------------------------- 738 # external API 739 #--------------------------------------------------------
740 - def refresh(self, address = None):
741 if address is not None: 742 self.address = address 743 744 if self.address is not None: 745 self._PRW_type.SetText(self.address['l10n_address_type']) 746 self._PRW_zip.SetText(self.address['postcode']) 747 self._PRW_street.SetText(self.address['street'], data = self.address['street']) 748 self._TCTRL_notes_street.SetValue(gmTools.coalesce(self.address['notes_street'], '')) 749 self._TCTRL_number.SetValue(self.address['number']) 750 self._TCTRL_subunit.SetValue(gmTools.coalesce(self.address['subunit'], '')) 751 self._PRW_suburb.SetText(gmTools.coalesce(self.address['suburb'], '')) 752 self._PRW_urb.SetText(self.address['urb'], data = self.address['urb']) 753 self._PRW_state.SetText(self.address['l10n_state'], data = self.address['code_state']) 754 self._PRW_country.SetText(self.address['l10n_country'], data = self.address['code_country']) 755 self._TCTRL_notes_subunit.SetValue(gmTools.coalesce(self.address['notes_subunit'], ''))
756 # FIXME: clear fields 757 # else: 758 # pass 759 #--------------------------------------------------------
760 - def save(self):
761 """Links address to patient, creating new address if necessary""" 762 763 if not self.__valid_for_save(): 764 return False 765 766 # link address to patient 767 try: 768 adr = self.identity.link_address ( 769 number = self._TCTRL_number.GetValue().strip(), 770 street = self._PRW_street.GetValue().strip(), 771 postcode = self._PRW_zip.GetValue().strip(), 772 urb = self._PRW_urb.GetValue().strip(), 773 state = self._PRW_state.GetData(), 774 country = self._PRW_country.GetData(), 775 subunit = gmTools.none_if(self._TCTRL_subunit.GetValue().strip(), u''), 776 suburb = gmTools.none_if(self._PRW_suburb.GetValue().strip(), u''), 777 id_type = self._PRW_type.GetData() 778 ) 779 except: 780 _log.exception('cannot save address') 781 gmGuiHelpers.gm_show_error ( 782 _('Cannot save address.\n\n' 783 'Does the state [%s]\n' 784 'exist in country [%s] ?' 785 ) % ( 786 self._PRW_state.GetValue().strip(), 787 self._PRW_country.GetValue().strip() 788 ), 789 _('Saving address') 790 ) 791 return False 792 793 notes = self._TCTRL_notes_street.GetValue().strip() 794 if notes != u'': 795 adr['notes_street'] = notes 796 notes = self._TCTRL_notes_subunit.GetValue().strip() 797 if notes != u'': 798 adr['notes_subunit'] = notes 799 adr.save_payload() 800 801 self.address = adr 802 803 return True
804 #-------------------------------------------------------- 805 # event handling 806 #--------------------------------------------------------
807 - def __register_interests(self):
808 self._PRW_zip.add_callback_on_lose_focus(self._on_zip_set) 809 self._PRW_country.add_callback_on_lose_focus(self._on_country_set)
810 #--------------------------------------------------------
811 - def _on_zip_set(self):
812 """Set the street, town, state and country according to entered zip code.""" 813 zip_code = self._PRW_zip.GetValue() 814 if zip_code.strip() == u'': 815 self._PRW_street.unset_context(context = u'zip') 816 self._PRW_urb.unset_context(context = u'zip') 817 self._PRW_state.unset_context(context = u'zip') 818 self._PRW_country.unset_context(context = u'zip') 819 else: 820 self._PRW_street.set_context(context = u'zip', val = zip_code) 821 self._PRW_urb.set_context(context = u'zip', val = zip_code) 822 self._PRW_state.set_context(context = u'zip', val = zip_code) 823 self._PRW_country.set_context(context = u'zip', val = zip_code)
824 #--------------------------------------------------------
825 - def _on_country_set(self):
826 """Set the states according to entered country.""" 827 country = self._PRW_country.GetData() 828 if country is None: 829 self._PRW_state.unset_context(context = 'country') 830 else: 831 self._PRW_state.set_context(context = 'country', val = country)
832 #-------------------------------------------------------- 833 # internal helpers 834 #--------------------------------------------------------
835 - def __valid_for_save(self):
836 837 # validate required fields 838 is_any_field_filled = False 839 840 required_fields = ( 841 self._PRW_type, 842 self._PRW_zip, 843 self._PRW_street, 844 self._TCTRL_number, 845 self._PRW_urb 846 ) 847 for field in required_fields: 848 if len(field.GetValue().strip()) == 0: 849 if is_any_field_filled: 850 field.SetBackgroundColour('pink') 851 field.SetFocus() 852 field.Refresh() 853 gmGuiHelpers.gm_show_error ( 854 _('Address details must be filled in completely or not at all.'), 855 _('Saving contact data') 856 ) 857 return False 858 else: 859 is_any_field_filled = True 860 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 861 field.Refresh() 862 863 required_fields = ( 864 self._PRW_state, 865 self._PRW_country 866 ) 867 for field in required_fields: 868 if field.GetData() is None: 869 if is_any_field_filled: 870 field.SetBackgroundColour('pink') 871 field.SetFocus() 872 field.Refresh() 873 gmGuiHelpers.gm_show_error ( 874 _('Address details must be filled in completely or not at all.'), 875 _('Saving contact data') 876 ) 877 return False 878 else: 879 is_any_field_filled = True 880 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 881 field.Refresh() 882 883 return True
884 #============================================================
885 -class cAddressMatchProvider(gmMatchProvider.cMatchProvider_SQL2):
886
887 - def __init__(self):
888 889 query = u""" 890 select * from ( 891 (select 892 pk_address, 893 (street || ' ' || number || coalesce(' (' || subunit || ')', '') || ', ' 894 || urb || coalesce(' (' || suburb || ')', '') || ', ' 895 || postcode 896 || coalesce(', ' || notes_street, '') 897 || coalesce(', ' || notes_subunit, '') 898 ) as address 899 from 900 dem.v_address 901 where 902 street %(fragment_condition)s 903 904 ) union ( 905 906 select 907 pk_address, 908 (street || ' ' || number || coalesce(' (' || subunit || ')', '') || ', ' 909 || urb || coalesce(' (' || suburb || ')', '') || ', ' 910 || postcode 911 || coalesce(', ' || notes_street, '') 912 || coalesce(', ' || notes_subunit, '') 913 ) as address 914 from 915 dem.v_address 916 where 917 postcode_street %(fragment_condition)s 918 919 ) union ( 920 921 select 922 pk_address, 923 (street || ' ' || number || coalesce(' (' || subunit || ')', '') || ', ' 924 || urb || coalesce(' (' || suburb || ')', '') || ', ' 925 || postcode 926 || coalesce(', ' || notes_street, '') 927 || coalesce(', ' || notes_subunit, '') 928 ) as address 929 from 930 dem.v_address 931 where 932 postcode_urb %(fragment_condition)s 933 ) 934 ) as union_result 935 order by union_result.address limit 50""" 936 937 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = query) 938 939 self.setThresholds(2, 4, 6)
940 # self.word_separators = u'[ \t]+' 941 942 #============================================================
943 -class cAddressPhraseWheel(gmPhraseWheel.cPhraseWheel):
944
945 - def __init__(self, *args, **kwargs):
946 947 mp = cAddressMatchProvider() 948 gmPhraseWheel.cPhraseWheel.__init__ ( 949 self, 950 *args, 951 **kwargs 952 ) 953 self.matcher = cAddressMatchProvider() 954 self.SetToolTipString(_('Select an address by postcode or street name.')) 955 self.selection_only = True 956 self.__address = None 957 self.__old_pk = None
958 #--------------------------------------------------------
959 - def get_address(self):
960 961 pk = self.GetData() 962 963 if pk is None: 964 self.__address = None 965 return None 966 967 if self.__address is None: 968 self.__old_pk = pk 969 self.__address = gmDemographicRecord.cAddress(aPK_obj = pk) 970 else: 971 if pk != self.__old_pk: 972 self.__old_pk = pk 973 self.__address = gmDemographicRecord.cAddress(aPK_obj = pk) 974 975 return self.__address
976 #============================================================
977 -class cAddressTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
978
979 - def __init__(self, *args, **kwargs):
980 981 query = u""" 982 select id, type from (( 983 select id, _(name) as type, 1 as rank 984 from dem.address_type 985 where _(name) %(fragment_condition)s 986 ) union ( 987 select id, name as type, 2 as rank 988 from dem.address_type 989 where name %(fragment_condition)s 990 )) as ur 991 order by 992 ur.rank, ur.type 993 """ 994 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 995 mp.setThresholds(1, 2, 4) 996 mp.word_separators = u'[ \t]+' 997 gmPhraseWheel.cPhraseWheel.__init__ ( 998 self, 999 *args, 1000 **kwargs 1001 ) 1002 self.matcher = mp 1003 self.SetToolTipString(_('Select the type of address.')) 1004 # self.capitalisation_mode = gmTools.CAPS_FIRST 1005 self.selection_only = True
1006 #-------------------------------------------------------- 1007 # def GetData(self, can_create=False): 1008 # if self.data is None: 1009 # if can_create: 1010 # self.data = gmDocuments.create_document_type(self.GetValue().strip())['pk_doc_type'] # FIXME: error handling 1011 # return self.data 1012 #============================================================
1013 -class cZipcodePhraseWheel(gmPhraseWheel.cPhraseWheel):
1014
1015 - def __init__(self, *args, **kwargs):
1016 # FIXME: add possible context 1017 query = u""" 1018 (select distinct postcode, postcode from dem.street where postcode %(fragment_condition)s limit 20) 1019 union 1020 (select distinct postcode, postcode from dem.urb where postcode %(fragment_condition)s limit 20)""" 1021 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1022 mp.setThresholds(2, 3, 15) 1023 gmPhraseWheel.cPhraseWheel.__init__ ( 1024 self, 1025 *args, 1026 **kwargs 1027 ) 1028 self.SetToolTipString(_("Type or select a zip code (postcode).")) 1029 self.matcher = mp
1030 #============================================================
1031 -class cStreetPhraseWheel(gmPhraseWheel.cPhraseWheel):
1032
1033 - def __init__(self, *args, **kwargs):
1034 context = { 1035 u'ctxt_zip': { 1036 u'where_part': u'and zip ilike %(zip)s', 1037 u'placeholder': u'zip' 1038 } 1039 } 1040 query = u""" 1041 select s1, s2 from ( 1042 select s1, s2, rank from ( 1043 select distinct on (street) 1044 street as s1, street as s2, 1 as rank 1045 from dem.v_zip2data 1046 where 1047 street %(fragment_condition)s 1048 %(ctxt_zip)s 1049 1050 union all 1051 1052 select distinct on (name) 1053 name as s1, name as s2, 2 as rank 1054 from dem.street 1055 where 1056 name %(fragment_condition)s 1057 1058 ) as q2 1059 ) as q1 order by rank, s2 limit 50""" 1060 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context) 1061 mp.setThresholds(3, 5, 8) 1062 gmPhraseWheel.cPhraseWheel.__init__ ( 1063 self, 1064 *args, 1065 **kwargs 1066 ) 1067 self.unset_context(context = u'zip') 1068 1069 self.SetToolTipString(_('Type or select a street.')) 1070 self.capitalisation_mode = gmTools.CAPS_FIRST 1071 self.matcher = mp
1072 #============================================================
1073 -class cSuburbPhraseWheel(gmPhraseWheel.cPhraseWheel):
1074
1075 - def __init__(self, *args, **kwargs):
1076 1077 query = """ 1078 select distinct on (suburb) suburb, suburb 1079 from dem.street 1080 where suburb %(fragment_condition)s 1081 order by suburb 1082 limit 50 1083 """ 1084 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1085 mp.setThresholds(2, 3, 6) 1086 gmPhraseWheel.cPhraseWheel.__init__ ( 1087 self, 1088 *args, 1089 **kwargs 1090 ) 1091 1092 self.SetToolTipString(_('Type or select the suburb.')) 1093 self.capitalisation_mode = gmTools.CAPS_FIRST 1094 self.matcher = mp
1095 #============================================================
1096 -class cUrbPhraseWheel(gmPhraseWheel.cPhraseWheel):
1097
1098 - def __init__(self, *args, **kwargs):
1099 context = { 1100 u'ctxt_zip': { 1101 u'where_part': u'and zip ilike %(zip)s', 1102 u'placeholder': u'zip' 1103 } 1104 } 1105 query = u""" 1106 select u1, u2 from ( 1107 select distinct on (rank, u1) 1108 u1, u2, rank 1109 from ( 1110 select 1111 urb as u1, urb as u2, 1 as rank 1112 from dem.v_zip2data 1113 where 1114 urb %(fragment_condition)s 1115 %(ctxt_zip)s 1116 1117 union all 1118 1119 select 1120 name as u1, name as u2, 2 as rank 1121 from dem.urb 1122 where 1123 name %(fragment_condition)s 1124 ) as union_result 1125 order by rank, u1 1126 ) as distincted_union 1127 limit 50 1128 """ 1129 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context) 1130 mp.setThresholds(3, 5, 7) 1131 gmPhraseWheel.cPhraseWheel.__init__ ( 1132 self, 1133 *args, 1134 **kwargs 1135 ) 1136 self.unset_context(context = u'zip') 1137 1138 self.SetToolTipString(_('Type or select a city/town/village/dwelling.')) 1139 self.capitalisation_mode = gmTools.CAPS_FIRST 1140 self.matcher = mp
1141 #============================================================ 1142 # communications channel related widgets 1143 #============================================================
1144 -class cCommChannelTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
1145
1146 - def __init__(self, *args, **kwargs):
1147 1148 query = u""" 1149 select pk, type from (( 1150 select pk, _(description) as type, 1 as rank 1151 from dem.enum_comm_types 1152 where _(description) %(fragment_condition)s 1153 ) union ( 1154 select pk, description as type, 2 as rank 1155 from dem.enum_comm_types 1156 where description %(fragment_condition)s 1157 )) as ur 1158 order by 1159 ur.rank, ur.type 1160 """ 1161 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1162 mp.setThresholds(1, 2, 4) 1163 mp.word_separators = u'[ \t]+' 1164 gmPhraseWheel.cPhraseWheel.__init__ ( 1165 self, 1166 *args, 1167 **kwargs 1168 ) 1169 self.matcher = mp 1170 self.SetToolTipString(_('Select the type of communications channel.')) 1171 self.selection_only = True
1172 #------------------------------------------------------------
1173 -class cCommChannelEditAreaPnl(wxgCommChannelEditAreaPnl.wxgCommChannelEditAreaPnl):
1174 """An edit area for editing/creating a comms channel. 1175 1176 Does NOT act on/listen to the current patient. 1177 """
1178 - def __init__(self, *args, **kwargs):
1179 try: 1180 self.channel = kwargs['comm_channel'] 1181 del kwargs['comm_channel'] 1182 except KeyError: 1183 self.channel = None 1184 1185 wxgCommChannelEditAreaPnl.wxgCommChannelEditAreaPnl.__init__(self, *args, **kwargs) 1186 1187 self.identity = None 1188 1189 self.refresh()
1190 #-------------------------------------------------------- 1191 # external API 1192 #--------------------------------------------------------
1193 - def refresh(self, comm_channel = None):
1194 if comm_channel is not None: 1195 self.channel = comm_channel 1196 1197 if self.channel is None: 1198 self._PRW_type.SetText(u'') 1199 self._TCTRL_url.SetValue(u'') 1200 self._PRW_address.SetText(value = u'', data = None) 1201 self._CHBOX_confidential.SetValue(False) 1202 else: 1203 self._PRW_type.SetText(self.channel['l10n_comm_type']) 1204 self._TCTRL_url.SetValue(self.channel['url']) 1205 self._PRW_address.SetData(data = self.channel['pk_address']) 1206 self._CHBOX_confidential.SetValue(self.channel['is_confidential'])
1207 #--------------------------------------------------------
1208 - def save(self):
1209 """Links comm channel to patient.""" 1210 if self.channel is None: 1211 if not self.__valid_for_save(): 1212 return False 1213 try: 1214 self.channel = self.identity.link_comm_channel ( 1215 pk_channel_type = self._PRW_type.GetData(), 1216 url = self._TCTRL_url.GetValue().strip(), 1217 is_confidential = self._CHBOX_confidential.GetValue(), 1218 ) 1219 except psycopg2.IntegrityError: 1220 _log.exception('error saving comm channel') 1221 gmDispatcher.send(signal = u'statustext', msg = _('Cannot save communications channel.'), beep = True) 1222 return False 1223 else: 1224 comm_type = self._PRW_type.GetValue().strip() 1225 if comm_type != u'': 1226 self.channel['comm_type'] = comm_type 1227 url = self._TCTRL_url.GetValue().strip() 1228 if url != u'': 1229 self.channel['url'] = url 1230 self.channel['is_confidential'] = self._CHBOX_confidential.GetValue() 1231 1232 self.channel['pk_address'] = self._PRW_address.GetData() 1233 self.channel.save_payload() 1234 1235 return True
1236 #-------------------------------------------------------- 1237 # internal helpers 1238 #--------------------------------------------------------
1239 - def __valid_for_save(self):
1240 1241 no_errors = True 1242 1243 if self._PRW_type.GetData() is None: 1244 self._PRW_type.SetBackgroundColour('pink') 1245 self._PRW_type.SetFocus() 1246 self._PRW_type.Refresh() 1247 no_errors = False 1248 else: 1249 self._PRW_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1250 self._PRW_type.Refresh() 1251 1252 if self._TCTRL_url.GetValue().strip() == u'': 1253 self._TCTRL_url.SetBackgroundColour('pink') 1254 self._TCTRL_url.SetFocus() 1255 self._TCTRL_url.Refresh() 1256 no_errors = False 1257 else: 1258 self._TCTRL_url.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1259 self._TCTRL_url.Refresh() 1260 1261 return no_errors
1262 #------------------------------------------------------------
1263 -class cPersonCommsManagerPnl(gmListWidgets.cGenericListManagerPnl):
1264 """A list for managing a person's comm channels. 1265 1266 Does NOT act on/listen to the current patient. 1267 """
1268 - def __init__(self, *args, **kwargs):
1269 1270 try: 1271 self.__identity = kwargs['identity'] 1272 del kwargs['identity'] 1273 except KeyError: 1274 self.__identity = None 1275 1276 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1277 1278 self.new_callback = self._add_comm 1279 self.edit_callback = self._edit_comm 1280 self.delete_callback = self._del_comm 1281 self.refresh_callback = self.refresh 1282 1283 self.__init_ui() 1284 self.refresh()
1285 #-------------------------------------------------------- 1286 # external API 1287 #--------------------------------------------------------
1288 - def refresh(self, *args, **kwargs):
1289 if self.__identity is None: 1290 self._LCTRL_items.set_string_items() 1291 return 1292 1293 comms = self.__identity.get_comm_channels() 1294 self._LCTRL_items.set_string_items ( 1295 items = [ [ gmTools.bool2str(c['is_confidential'], u'X', u''), c['l10n_comm_type'], c['url'] ] for c in comms ] 1296 ) 1297 self._LCTRL_items.set_column_widths() 1298 self._LCTRL_items.set_data(data = comms)
1299 #-------------------------------------------------------- 1300 # internal helpers 1301 #--------------------------------------------------------
1302 - def __init_ui(self):
1303 self._LCTRL_items.SetToolTipString(_('List of known communication channels.')) 1304 self._LCTRL_items.set_columns(columns = [ 1305 _('confidential'), 1306 _('Type'), 1307 _('Value') 1308 ])
1309 #--------------------------------------------------------
1310 - def _add_comm(self):
1311 ea = cCommChannelEditAreaPnl(self, -1) 1312 ea.identity = self.__identity 1313 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1314 dlg.SetTitle(_('Adding new communications channel')) 1315 if dlg.ShowModal() == wx.ID_OK: 1316 return True 1317 return False
1318 #--------------------------------------------------------
1319 - def _edit_comm(self, comm_channel):
1320 ea = cCommChannelEditAreaPnl(self, -1, comm_channel = comm_channel) 1321 ea.identity = self.__identity 1322 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1323 dlg.SetTitle(_('Editing communications channel')) 1324 if dlg.ShowModal() == wx.ID_OK: 1325 return True 1326 return False
1327 #--------------------------------------------------------
1328 - def _del_comm(self, comm):
1329 go_ahead = gmGuiHelpers.gm_show_question ( 1330 _( 'Are you sure this patient can no longer\n' 1331 "be contacted via this channel ?" 1332 ), 1333 _('Removing communication channel') 1334 ) 1335 if not go_ahead: 1336 return False 1337 self.__identity.unlink_comm_channel(comm_channel = comm) 1338 return True
1339 #-------------------------------------------------------- 1340 # properties 1341 #--------------------------------------------------------
1342 - def _get_identity(self):
1343 return self.__identity
1344
1345 - def _set_identity(self, identity):
1346 self.__identity = identity 1347 self.refresh()
1348 1349 identity = property(_get_identity, _set_identity)
1350 #============================================================ 1351 # identity widgets 1352 #============================================================ 1353 # phrasewheels 1354 #------------------------------------------------------------
1355 -class cLastnamePhraseWheel(gmPhraseWheel.cPhraseWheel):
1356
1357 - def __init__(self, *args, **kwargs):
1358 query = u"select distinct lastnames, lastnames from dem.names where lastnames %(fragment_condition)s order by lastnames limit 25" 1359 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1360 mp.setThresholds(3, 5, 9) 1361 gmPhraseWheel.cPhraseWheel.__init__ ( 1362 self, 1363 *args, 1364 **kwargs 1365 ) 1366 self.SetToolTipString(_("Type or select a last name (family name/surname).")) 1367 self.capitalisation_mode = gmTools.CAPS_NAMES 1368 self.matcher = mp
1369 #------------------------------------------------------------
1370 -class cFirstnamePhraseWheel(gmPhraseWheel.cPhraseWheel):
1371
1372 - def __init__(self, *args, **kwargs):
1373 query = u""" 1374 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20) 1375 union 1376 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)""" 1377 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1378 mp.setThresholds(3, 5, 9) 1379 gmPhraseWheel.cPhraseWheel.__init__ ( 1380 self, 1381 *args, 1382 **kwargs 1383 ) 1384 self.SetToolTipString(_("Type or select a first name (forename/Christian name/given name).")) 1385 self.capitalisation_mode = gmTools.CAPS_NAMES 1386 self.matcher = mp
1387 #------------------------------------------------------------
1388 -class cNicknamePhraseWheel(gmPhraseWheel.cPhraseWheel):
1389
1390 - def __init__(self, *args, **kwargs):
1391 query = u""" 1392 (select distinct preferred, preferred from dem.names where preferred %(fragment_condition)s order by preferred limit 20) 1393 union 1394 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20) 1395 union 1396 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)""" 1397 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1398 mp.setThresholds(3, 5, 9) 1399 gmPhraseWheel.cPhraseWheel.__init__ ( 1400 self, 1401 *args, 1402 **kwargs 1403 ) 1404 self.SetToolTipString(_("Type or select an alias (nick name, preferred name, call name, warrior name, artist name).")) 1405 # nicknames CAN start with lower case ! 1406 #self.capitalisation_mode = gmTools.CAPS_NAMES 1407 self.matcher = mp
1408 #------------------------------------------------------------
1409 -class cTitlePhraseWheel(gmPhraseWheel.cPhraseWheel):
1410
1411 - def __init__(self, *args, **kwargs):
1412 query = u"select distinct title, title from dem.identity where title %(fragment_condition)s" 1413 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1414 mp.setThresholds(1, 3, 9) 1415 gmPhraseWheel.cPhraseWheel.__init__ ( 1416 self, 1417 *args, 1418 **kwargs 1419 ) 1420 self.SetToolTipString(_("Type or select a title. Note that the title applies to the person, not to a particular name !")) 1421 self.matcher = mp
1422 #------------------------------------------------------------
1423 -class cGenderSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
1424 """Let user select a gender.""" 1425 1426 _gender_map = None 1427
1428 - def __init__(self, *args, **kwargs):
1429 1430 if cGenderSelectionPhraseWheel._gender_map is None: 1431 cmd = u""" 1432 select tag, l10n_label, sort_weight 1433 from dem.v_gender_labels 1434 order by sort_weight desc""" 1435 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True) 1436 cGenderSelectionPhraseWheel._gender_map = {} 1437 for gender in rows: 1438 cGenderSelectionPhraseWheel._gender_map[gender[idx['tag']]] = { 1439 'data': gender[idx['tag']], 1440 'label': gender[idx['l10n_label']], 1441 'weight': gender[idx['sort_weight']] 1442 } 1443 1444 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = cGenderSelectionPhraseWheel._gender_map.values()) 1445 mp.setThresholds(1, 1, 3) 1446 1447 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 1448 self.selection_only = True 1449 self.matcher = mp 1450 self.picklist_delay = 50
1451 #------------------------------------------------------------
1452 -class cOccupationPhraseWheel(gmPhraseWheel.cPhraseWheel):
1453
1454 - def __init__(self, *args, **kwargs):
1455 query = u"select distinct name, _(name) from dem.occupation where _(name) %(fragment_condition)s" 1456 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1457 mp.setThresholds(1, 3, 5) 1458 gmPhraseWheel.cPhraseWheel.__init__ ( 1459 self, 1460 *args, 1461 **kwargs 1462 ) 1463 self.SetToolTipString(_("Type or select an occupation.")) 1464 self.capitalisation_mode = gmTools.CAPS_FIRST 1465 self.matcher = mp
1466 #------------------------------------------------------------
1467 -class cExternalIDTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
1468
1469 - def __init__(self, *args, **kwargs):
1470 query = u""" 1471 select distinct pk, (name || coalesce(' (%s ' || issuer || ')', '')) as label 1472 from dem.enum_ext_id_types 1473 where name %%(fragment_condition)s 1474 order by label limit 25""" % _('issued by') 1475 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1476 mp.setThresholds(1, 3, 5) 1477 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 1478 self.SetToolTipString(_("Enter or select a type for the external ID.")) 1479 self.matcher = mp
1480 #------------------------------------------------------------
1481 -class cExternalIDIssuerPhraseWheel(gmPhraseWheel.cPhraseWheel):
1482
1483 - def __init__(self, *args, **kwargs):
1484 query = u""" 1485 select distinct issuer, issuer 1486 from dem.enum_ext_id_types 1487 where issuer %(fragment_condition)s 1488 order by issuer limit 25""" 1489 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1490 mp.setThresholds(1, 3, 5) 1491 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 1492 self.SetToolTipString(_("Type or select an occupation.")) 1493 self.capitalisation_mode = gmTools.CAPS_FIRST 1494 self.matcher = mp
1495 #------------------------------------------------------------ 1496 # edit areas 1497 #------------------------------------------------------------
1498 -class cExternalIDEditAreaPnl(wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl):
1499 """An edit area for editing/creating external IDs. 1500 1501 Does NOT act on/listen to the current patient. 1502 """
1503 - def __init__(self, *args, **kwargs):
1504 1505 try: 1506 self.ext_id = kwargs['external_id'] 1507 del kwargs['external_id'] 1508 except: 1509 self.ext_id = None 1510 1511 wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl.__init__(self, *args, **kwargs) 1512 1513 self.identity = None 1514 1515 self.__register_events() 1516 1517 self.refresh()
1518 #-------------------------------------------------------- 1519 # external API 1520 #--------------------------------------------------------
1521 - def refresh(self, ext_id=None):
1522 if ext_id is not None: 1523 self.ext_id = ext_id 1524 1525 if self.ext_id is not None: 1526 self._PRW_type.SetText(value = self.ext_id['name'], data = self.ext_id['pk_type']) 1527 self._TCTRL_value.SetValue(self.ext_id['value']) 1528 self._PRW_issuer.SetText(self.ext_id['issuer']) 1529 self._TCTRL_comment.SetValue(gmTools.coalesce(self.ext_id['comment'], u''))
1530 # FIXME: clear fields 1531 # else: 1532 # pass 1533 #--------------------------------------------------------
1534 - def save(self):
1535 1536 if not self.__valid_for_save(): 1537 return False 1538 1539 # strip out " (issued by ...)" added by phrasewheel 1540 type = regex.split(' \(%s .+\)$' % _('issued by'), self._PRW_type.GetValue().strip(), 1)[0] 1541 1542 # add new external ID 1543 if self.ext_id is None: 1544 self.identity.add_external_id ( 1545 type_name = type, 1546 value = self._TCTRL_value.GetValue().strip(), 1547 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''), 1548 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 1549 ) 1550 # edit old external ID 1551 else: 1552 self.identity.update_external_id ( 1553 pk_id = self.ext_id['pk_id'], 1554 type = type, 1555 value = self._TCTRL_value.GetValue().strip(), 1556 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''), 1557 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 1558 ) 1559 1560 return True
1561 #-------------------------------------------------------- 1562 # internal helpers 1563 #--------------------------------------------------------
1564 - def __register_events(self):
1565 self._PRW_type.add_callback_on_lose_focus(self._on_type_set)
1566 #--------------------------------------------------------
1567 - def _on_type_set(self):
1568 """Set the issuer according to the selected type. 1569 1570 Matches are fetched from existing records in backend. 1571 """ 1572 pk_curr_type = self._PRW_type.GetData() 1573 if pk_curr_type is None: 1574 return True 1575 rows, idx = gmPG2.run_ro_queries(queries = [{ 1576 'cmd': u"select issuer from dem.enum_ext_id_types where pk = %s", 1577 'args': [pk_curr_type] 1578 }]) 1579 if len(rows) == 0: 1580 return True 1581 wx.CallAfter(self._PRW_issuer.SetText, rows[0][0]) 1582 return True
1583 #--------------------------------------------------------
1584 - def __valid_for_save(self):
1585 1586 no_errors = True 1587 1588 # do not test .GetData() because adding external IDs 1589 # will create types if necessary 1590 # if self._PRW_type.GetData() is None: 1591 if self._PRW_type.GetValue().strip() == u'': 1592 self._PRW_type.SetBackgroundColour('pink') 1593 self._PRW_type.SetFocus() 1594 self._PRW_type.Refresh() 1595 else: 1596 self._PRW_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1597 self._PRW_type.Refresh() 1598 1599 if self._TCTRL_value.GetValue().strip() == u'': 1600 self._TCTRL_value.SetBackgroundColour('pink') 1601 self._TCTRL_value.SetFocus() 1602 self._TCTRL_value.Refresh() 1603 no_errors = False 1604 else: 1605 self._TCTRL_value.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1606 self._TCTRL_value.Refresh() 1607 1608 return no_errors
1609 #------------------------------------------------------------ 1610 from Gnumed.wxGladeWidgets import wxgIdentityEAPnl 1611
1612 -class cIdentityEAPnl(wxgIdentityEAPnl.wxgIdentityEAPnl, gmEditArea.cGenericEditAreaMixin):
1613 """An edit area for editing/creating title/gender/dob/dod etc.""" 1614
1615 - def __init__(self, *args, **kwargs):
1616 1617 try: 1618 data = kwargs['identity'] 1619 del kwargs['identity'] 1620 except KeyError: 1621 data = None 1622 1623 wxgIdentityEAPnl.wxgIdentityEAPnl.__init__(self, *args, **kwargs) 1624 gmEditArea.cGenericEditAreaMixin.__init__(self) 1625 1626 self.mode = 'new' 1627 self.data = data 1628 if data is not None: 1629 self.mode = 'edit'
1630 1631 # self.__init_ui() 1632 #---------------------------------------------------------------- 1633 # def __init_ui(self): 1634 # # adjust phrasewheels etc 1635 #---------------------------------------------------------------- 1636 # generic Edit Area mixin API 1637 #----------------------------------------------------------------
1638 - def _valid_for_save(self):
1639 1640 has_error = False 1641 1642 if self._PRW_gender.GetData() is None: 1643 self._PRW_gender.SetFocus() 1644 has_error = True 1645 1646 if not self._PRW_dob.is_valid_timestamp(): 1647 val = self._PRW_dob.GetValue().strip() 1648 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val) 1649 self._PRW_dob.SetBackgroundColour('pink') 1650 self._PRW_dob.Refresh() 1651 self._PRW_dob.SetFocus() 1652 has_error = True 1653 else: 1654 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1655 self._PRW_dob.Refresh() 1656 1657 if not self._DP_dod.is_valid_timestamp(allow_none=True): 1658 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.')) 1659 self._DP_dod.SetFocus() 1660 has_error = True 1661 1662 return (has_error is False)
1663 #----------------------------------------------------------------
1664 - def _save_as_new(self):
1665 # save the data as a new instance 1666 # data = 1 1667 1668 # data[''] = 1 1669 # data[''] = 1 1670 1671 # data.save() 1672 1673 # must be done very late or else the property access 1674 # will refresh the display such that later field 1675 # access will return empty values 1676 # self.data = data 1677 return False 1678 return True
1679 #----------------------------------------------------------------
1680 - def _save_as_update(self):
1681 1682 self.data['gender'] = self._PRW_gender.GetData() 1683 1684 if self._PRW_dob.GetValue().strip() == u'': 1685 self.data['dob'] = None 1686 else: 1687 self.data['dob'] = self._PRW_dob.GetData().get_pydt() 1688 1689 self.data['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'') 1690 self.data['deceased'] = self._DP_dod.GetValue(as_pydt = True) 1691 1692 self.data.save() 1693 return True
1694 #----------------------------------------------------------------
1695 - def _refresh_as_new(self):
1696 pass
1697 #----------------------------------------------------------------
1698 - def _refresh_from_existing(self):
1699 1700 self._LBL_info.SetLabel(u'ID: #%s' % ( 1701 self.data.ID 1702 # FIXME: add 'deleted' status 1703 )) 1704 self._PRW_dob.SetText ( 1705 value = self.data.get_formatted_dob(format = '%Y-%m-%d %H:%M', encoding = gmI18N.get_encoding()), 1706 data = self.data['dob'] 1707 ) 1708 self._DP_dod.SetValue(self.data['deceased']) 1709 self._PRW_gender.SetData(self.data['gender']) 1710 #self._PRW_ethnicity.SetValue() 1711 self._PRW_title.SetText(gmTools.coalesce(self.data['title'], u''))
1712 #----------------------------------------------------------------
1714 pass
1715 #---------------------------------------------------------------- 1716 1717 #------------------------------------------------------------ 1718 from Gnumed.wxGladeWidgets import wxgNameGenderDOBEditAreaPnl 1719
1720 -class cNameGenderDOBEditAreaPnl(wxgNameGenderDOBEditAreaPnl.wxgNameGenderDOBEditAreaPnl):
1721 """An edit area for editing/creating name/gender/dob. 1722 1723 Does NOT act on/listen to the current patient. 1724 """
1725 - def __init__(self, *args, **kwargs):
1726 1727 self.__name = kwargs['name'] 1728 del kwargs['name'] 1729 self.__identity = gmPerson.cIdentity(aPK_obj = self.__name['pk_identity']) 1730 1731 wxgNameGenderDOBEditAreaPnl.wxgNameGenderDOBEditAreaPnl.__init__(self, *args, **kwargs) 1732 1733 self.__register_interests() 1734 self.refresh()
1735 #-------------------------------------------------------- 1736 # external API 1737 #--------------------------------------------------------
1738 - def refresh(self):
1739 if self.__name is None: 1740 return 1741 1742 self._PRW_title.SetText(gmTools.coalesce(self.__name['title'], u'')) 1743 self._PRW_firstname.SetText(self.__name['firstnames']) 1744 self._PRW_lastname.SetText(self.__name['lastnames']) 1745 self._PRW_nick.SetText(gmTools.coalesce(self.__name['preferred'], u'')) 1746 self._PRW_dob.SetText ( 1747 value = self.__identity.get_formatted_dob(format = '%Y-%m-%d %H:%M', encoding = gmI18N.get_encoding()), 1748 data = self.__identity['dob'] 1749 ) 1750 self._PRW_gender.SetData(self.__name['gender']) 1751 self._CHBOX_active.SetValue(self.__name['active_name']) 1752 self._DP_dod.SetValue(self.__identity['deceased']) 1753 self._TCTRL_comment.SetValue(gmTools.coalesce(self.__name['comment'], u''))
1754 # FIXME: clear fields 1755 # else: 1756 # pass 1757 #--------------------------------------------------------
1758 - def save(self):
1759 1760 if not self.__valid_for_save(): 1761 return False 1762 1763 self.__identity['gender'] = self._PRW_gender.GetData() 1764 if self._PRW_dob.GetValue().strip() == u'': 1765 self.__identity['dob'] = None 1766 else: 1767 self.__identity['dob'] = self._PRW_dob.GetData().get_pydt() 1768 self.__identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'') 1769 self.__identity['deceased'] = self._DP_dod.GetValue(as_pydt = True) 1770 self.__identity.save_payload() 1771 1772 active = self._CHBOX_active.GetValue() 1773 first = self._PRW_firstname.GetValue().strip() 1774 last = self._PRW_lastname.GetValue().strip() 1775 old_nick = self.__name['preferred'] 1776 1777 # is it a new name ? 1778 old_name = self.__name['firstnames'] + self.__name['lastnames'] 1779 if (first + last) != old_name: 1780 self.__name = self.__identity.add_name(first, last, active) 1781 1782 self.__name['active_name'] = active 1783 self.__name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'') 1784 self.__name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 1785 1786 self.__name.save_payload() 1787 1788 return True
1789 #-------------------------------------------------------- 1790 # event handling 1791 #--------------------------------------------------------
1792 - def __register_interests(self):
1793 self._PRW_firstname.add_callback_on_lose_focus(self._on_name_set)
1794 #--------------------------------------------------------
1795 - def _on_name_set(self):
1796 """Set the gender according to entered firstname. 1797 1798 Matches are fetched from existing records in backend. 1799 """ 1800 firstname = self._PRW_firstname.GetValue().strip() 1801 if firstname == u'': 1802 return True 1803 rows, idx = gmPG2.run_ro_queries(queries = [{ 1804 'cmd': u"select gender from dem.name_gender_map where name ilike %s", 1805 'args': [firstname] 1806 }]) 1807 if len(rows) == 0: 1808 return True 1809 wx.CallAfter(self._PRW_gender.SetData, rows[0][0]) 1810 return True
1811 #-------------------------------------------------------- 1812 # internal helpers 1813 #--------------------------------------------------------
1814 - def __valid_for_save(self):
1815 1816 has_error = False 1817 1818 if self._PRW_gender.GetData() is None: 1819 self._PRW_gender.SetBackgroundColour('pink') 1820 self._PRW_gender.Refresh() 1821 self._PRW_gender.SetFocus() 1822 has_error = True 1823 else: 1824 self._PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1825 self._PRW_gender.Refresh() 1826 1827 if not self._PRW_dob.is_valid_timestamp(): 1828 val = self._PRW_dob.GetValue().strip() 1829 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val) 1830 self._PRW_dob.SetBackgroundColour('pink') 1831 self._PRW_dob.Refresh() 1832 self._PRW_dob.SetFocus() 1833 has_error = True 1834 else: 1835 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1836 self._PRW_dob.Refresh() 1837 1838 if not self._DP_dod.is_valid_timestamp(): 1839 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.')) 1840 self._DP_dod.SetBackgroundColour('pink') 1841 self._DP_dod.Refresh() 1842 self._DP_dod.SetFocus() 1843 has_error = True 1844 else: 1845 self._DP_dod.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1846 self._DP_dod.Refresh() 1847 1848 if self._PRW_lastname.GetValue().strip() == u'': 1849 self._PRW_lastname.SetBackgroundColour('pink') 1850 self._PRW_lastname.Refresh() 1851 self._PRW_lastname.SetFocus() 1852 has_error = True 1853 else: 1854 self._PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1855 self._PRW_lastname.Refresh() 1856 1857 if self._PRW_firstname.GetValue().strip() == u'': 1858 self._PRW_firstname.SetBackgroundColour('pink') 1859 self._PRW_firstname.Refresh() 1860 self._PRW_firstname.SetFocus() 1861 has_error = True 1862 else: 1863 self._PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1864 self._PRW_firstname.Refresh() 1865 1866 return (has_error is False)
1867 #------------------------------------------------------------ 1868 # list manager 1869 #------------------------------------------------------------
1870 -class cPersonNamesManagerPnl(gmListWidgets.cGenericListManagerPnl):
1871 """A list for managing a person's names. 1872 1873 Does NOT act on/listen to the current patient. 1874 """
1875 - def __init__(self, *args, **kwargs):
1876 1877 try: 1878 self.__identity = kwargs['identity'] 1879 del kwargs['identity'] 1880 except KeyError: 1881 self.__identity = None 1882 1883 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1884 1885 self.new_callback = self._add_name 1886 self.edit_callback = self._edit_name 1887 self.delete_callback = self._del_name 1888 self.refresh_callback = self.refresh 1889 1890 self.__init_ui() 1891 self.refresh()
1892 #-------------------------------------------------------- 1893 # external API 1894 #--------------------------------------------------------
1895 - def refresh(self, *args, **kwargs):
1896 if self.__identity is None: 1897 self._LCTRL_items.set_string_items() 1898 return 1899 1900 names = self.__identity.get_names() 1901 self._LCTRL_items.set_string_items ( 1902 items = [ [ 1903 gmTools.bool2str(n['active_name'], 'X', ''), 1904 n['lastnames'], 1905 n['firstnames'], 1906 gmTools.coalesce(n['preferred'], u''), 1907 gmTools.coalesce(n['comment'], u'') 1908 ] for n in names ] 1909 ) 1910 self._LCTRL_items.set_column_widths() 1911 self._LCTRL_items.set_data(data = names)
1912 #-------------------------------------------------------- 1913 # internal helpers 1914 #--------------------------------------------------------
1915 - def __init_ui(self):
1916 self._LCTRL_items.set_columns(columns = [ 1917 _('Active'), 1918 _('Lastname'), 1919 _('Firstname(s)'), 1920 _('Preferred Name'), 1921 _('Comment') 1922 ])
1923 #--------------------------------------------------------
1924 - def _add_name(self):
1925 ea = cNameGenderDOBEditAreaPnl(self, -1, name = self.__identity.get_active_name()) 1926 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1927 dlg.SetTitle(_('Adding new name')) 1928 if dlg.ShowModal() == wx.ID_OK: 1929 dlg.Destroy() 1930 return True 1931 dlg.Destroy() 1932 return False
1933 #--------------------------------------------------------
1934 - def _edit_name(self, name):
1935 ea = cNameGenderDOBEditAreaPnl(self, -1, name = name) 1936 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1937 dlg.SetTitle(_('Editing name')) 1938 if dlg.ShowModal() == wx.ID_OK: 1939 dlg.Destroy() 1940 return True 1941 dlg.Destroy() 1942 return False
1943 #--------------------------------------------------------
1944 - def _del_name(self, name):
1945 1946 if len(self.__identity.get_names()) == 1: 1947 gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the only name of a person.'), beep = True) 1948 return False 1949 1950 go_ahead = gmGuiHelpers.gm_show_question ( 1951 _( 'It is often advisable to keep old names around and\n' 1952 'just create a new "currently active" name.\n' 1953 '\n' 1954 'This allows finding the patient by both the old\n' 1955 'and the new name (think before/after marriage).\n' 1956 '\n' 1957 'Do you still want to really delete\n' 1958 "this name from the patient ?" 1959 ), 1960 _('Deleting name') 1961 ) 1962 if not go_ahead: 1963 return False 1964 1965 self.__identity.delete_name(name = name) 1966 return True
1967 #-------------------------------------------------------- 1968 # properties 1969 #--------------------------------------------------------
1970 - def _get_identity(self):
1971 return self.__identity
1972
1973 - def _set_identity(self, identity):
1974 self.__identity = identity 1975 self.refresh()
1976 1977 identity = property(_get_identity, _set_identity)
1978 #------------------------------------------------------------
1979 -class cPersonIDsManagerPnl(gmListWidgets.cGenericListManagerPnl):
1980 """A list for managing a person's external IDs. 1981 1982 Does NOT act on/listen to the current patient. 1983 """
1984 - def __init__(self, *args, **kwargs):
1985 1986 try: 1987 self.__identity = kwargs['identity'] 1988 del kwargs['identity'] 1989 except KeyError: 1990 self.__identity = None 1991 1992 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1993 1994 self.new_callback = self._add_id 1995 self.edit_callback = self._edit_id 1996 self.delete_callback = self._del_id 1997 self.refresh_callback = self.refresh 1998 1999 self.__init_ui() 2000 self.refresh()
2001 #-------------------------------------------------------- 2002 # external API 2003 #--------------------------------------------------------
2004 - def refresh(self, *args, **kwargs):
2005 if self.__identity is None: 2006 self._LCTRL_items.set_string_items() 2007 return 2008 2009 ids = self.__identity.get_external_ids() 2010 self._LCTRL_items.set_string_items ( 2011 items = [ [ 2012 i['name'], 2013 i['value'], 2014 gmTools.coalesce(i['issuer'], u''), 2015 i['context'], 2016 gmTools.coalesce(i['comment'], u'') 2017 ] for i in ids 2018 ] 2019 ) 2020 self._LCTRL_items.set_column_widths() 2021 self._LCTRL_items.set_data(data = ids)
2022 #-------------------------------------------------------- 2023 # internal helpers 2024 #--------------------------------------------------------
2025 - def __init_ui(self):
2026 self._LCTRL_items.set_columns(columns = [ 2027 _('ID type'), 2028 _('Value'), 2029 _('Issuer'), 2030 _('Context'), 2031 _('Comment') 2032 ])
2033 #--------------------------------------------------------
2034 - def _add_id(self):
2035 ea = cExternalIDEditAreaPnl(self, -1) 2036 ea.identity = self.__identity 2037 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 2038 dlg.SetTitle(_('Adding new external ID')) 2039 if dlg.ShowModal() == wx.ID_OK: 2040 dlg.Destroy() 2041 return True 2042 dlg.Destroy() 2043 return False
2044 #--------------------------------------------------------
2045 - def _edit_id(self, ext_id):
2046 ea = cExternalIDEditAreaPnl(self, -1, external_id = ext_id) 2047 ea.identity = self.__identity 2048 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 2049 dlg.SetTitle(_('Editing external ID')) 2050 if dlg.ShowModal() == wx.ID_OK: 2051 dlg.Destroy() 2052 return True 2053 dlg.Destroy() 2054 return False
2055 #--------------------------------------------------------
2056 - def _del_id(self, ext_id):
2057 go_ahead = gmGuiHelpers.gm_show_question ( 2058 _( 'Do you really want to delete this\n' 2059 'external ID from the patient ?'), 2060 _('Deleting external ID') 2061 ) 2062 if not go_ahead: 2063 return False 2064 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id']) 2065 return True
2066 #-------------------------------------------------------- 2067 # properties 2068 #--------------------------------------------------------
2069 - def _get_identity(self):
2070 return self.__identity
2071
2072 - def _set_identity(self, identity):
2073 self.__identity = identity 2074 self.refresh()
2075 2076 identity = property(_get_identity, _set_identity)
2077 #------------------------------------------------------------ 2078 # integrated panels 2079 #------------------------------------------------------------
2080 -class cPersonIdentityManagerPnl(wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl):
2081 """A panel for editing identity data for a person. 2082 2083 - provides access to: 2084 - name 2085 - external IDs 2086 2087 Does NOT act on/listen to the current patient. 2088 """
2089 - def __init__(self, *args, **kwargs):
2090 2091 wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl.__init__(self, *args, **kwargs) 2092 2093 self.__identity = None 2094 self.refresh()
2095 #-------------------------------------------------------- 2096 # external API 2097 #--------------------------------------------------------
2098 - def refresh(self):
2099 self._PNL_names.identity = self.__identity 2100 self._PNL_ids.identity = self.__identity 2101 # this is an Edit Area: 2102 self._PNL_identity.mode = 'new' 2103 self._PNL_identity.data = self.__identity 2104 if self.__identity is not None: 2105 self._PNL_identity.mode = 'edit'
2106 #-------------------------------------------------------- 2107 # properties 2108 #--------------------------------------------------------
2109 - def _get_identity(self):
2110 return self.__identity
2111
2112 - def _set_identity(self, identity):
2113 self.__identity = identity 2114 self.refresh()
2115 2116 identity = property(_get_identity, _set_identity) 2117 #-------------------------------------------------------- 2118 # event handlers 2119 #--------------------------------------------------------
2121 if not self._PNL_identity.save(): 2122 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save identity. Incomplete information'), beep = True)
2123 #--------------------------------------------------------
2124 - def _on_reload_identity_button_pressed(self, event):
2125 self._PNL_identity.refresh()
2126 2127 #============================================================ 2128 from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl 2129
2130 -class cPersonSocialNetworkManagerPnl(wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl):
2131 - def __init__(self, *args, **kwargs):
2132 2133 wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl.__init__(self, *args, **kwargs) 2134 2135 self.__identity = None 2136 self.refresh()
2137 #-------------------------------------------------------- 2138 # external API 2139 #--------------------------------------------------------
2140 - def refresh(self):
2141 2142 tt = _("Link another person in this database as the emergency contact.") 2143 2144 if self.__identity is None: 2145 self._TCTRL_er_contact.SetValue(u'') 2146 self._TCTRL_person.person = None 2147 self._TCTRL_person.SetToolTipString(tt) 2148 return 2149 2150 self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], u'')) 2151 if self.__identity['pk_emergency_contact'] is not None: 2152 ident = gmPerson.cIdentity(aPK_obj = self.__identity['pk_emergency_contact']) 2153 self._TCTRL_person.person = ident 2154 tt = u'%s\n\n%s\n\n%s' % ( 2155 tt, 2156 ident['description_gender'], 2157 u'\n'.join([ 2158 u'%s: %s%s' % ( 2159 c['l10n_comm_type'], 2160 c['url'], 2161 gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), u'', u'') 2162 ) 2163 for c in ident.get_comm_channels() 2164 ]) 2165 ) 2166 else: 2167 self._TCTRL_person.person = None 2168 2169 self._TCTRL_person.SetToolTipString(tt)
2170 #-------------------------------------------------------- 2171 # properties 2172 #--------------------------------------------------------
2173 - def _get_identity(self):
2174 return self.__identity
2175
2176 - def _set_identity(self, identity):
2177 self.__identity = identity 2178 self.refresh()
2179 2180 identity = property(_get_identity, _set_identity) 2181 #-------------------------------------------------------- 2182 # event handlers 2183 #--------------------------------------------------------
2184 - def _on_save_button_pressed(self, event):
2185 if self.__identity is not None: 2186 self.__identity['emergency_contact'] = self._TCTRL_er_contact.GetValue().strip() 2187 self.__identity['pk_emergency_contact'] = self._TCTRL_person.person.ID 2188 self.__identity.save() 2189 2190 event.Skip()
2191 #--------------------------------------------------------
2192 - def _on_button_activate_contact_pressed(self, event):
2193 2194 ident = self._TCTRL_person.person 2195 if ident is not None: 2196 from Gnumed.wxpython import gmPatSearchWidgets 2197 gmPatSearchWidgets.set_active_patient(patient = ident, forced_reload = False) 2198 2199 event.Skip()
2200 #============================================================ 2201 # new-patient widgets 2202 #============================================================
2203 -def create_new_person(parent=None, activate=False):
2204 2205 dbcfg = gmCfg.cCfgSQL() 2206 2207 def_region = dbcfg.get2 ( 2208 option = u'person.create.default_region', 2209 workplace = gmSurgery.gmCurrentPractice().active_workplace, 2210 bias = u'user' 2211 ) 2212 2213 if def_region is None: 2214 def_country = dbcfg.get2 ( 2215 option = u'person.create.default_country', 2216 workplace = gmSurgery.gmCurrentPractice().active_workplace, 2217 bias = u'user' 2218 ) 2219 else: 2220 countries = gmDemographicRecord.get_country_for_region(region = def_region) 2221 if len(countries) == 1: 2222 def_country = countries[0]['l10n_country'] 2223 2224 if parent is None: 2225 parent = wx.GetApp().GetTopWindow() 2226 2227 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region) 2228 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) 2229 dlg.SetTitle(_('Adding new person')) 2230 ea._PRW_lastname.SetFocus() 2231 result = dlg.ShowModal() 2232 pat = ea.data 2233 dlg.Destroy() 2234 2235 if result != wx.ID_OK: 2236 return False 2237 2238 if activate: 2239 from Gnumed.wxpython import gmPatSearchWidgets 2240 gmPatSearchWidgets.set_active_patient(patient = pat) 2241 2242 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin') 2243 2244 return True
2245 #============================================================ 2246 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl 2247
2248 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
2249
2250 - def __init__(self, *args, **kwargs):
2251 2252 try: 2253 self.default_region = kwargs['region'] 2254 del kwargs['region'] 2255 except KeyError: 2256 self.default_region = None 2257 2258 try: 2259 self.default_country = kwargs['country'] 2260 del kwargs['country'] 2261 except KeyError: 2262 self.default_country = None 2263 2264 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs) 2265 gmEditArea.cGenericEditAreaMixin.__init__(self) 2266 2267 self.mode = 'new' 2268 self.data = None 2269 self._address = None 2270 2271 self.__init_ui() 2272 self.__register_interests()
2273 #---------------------------------------------------------------- 2274 # internal helpers 2275 #----------------------------------------------------------------
2276 - def __init_ui(self):
2277 self._PRW_lastname.final_regex = '.+' 2278 self._PRW_firstnames.final_regex = '.+' 2279 self._PRW_address_searcher.selection_only = False 2280 low = wx.DateTimeFromDMY(1,0,1900) 2281 hi = wx.DateTime() 2282 self._DP_dob.SetRange(low, hi.SetToCurrent()) 2283 # only if we would support None on selection_only's 2284 #self._PRW_external_id_type.selection_only = True 2285 2286 if self.default_country is not None: 2287 self._PRW_country.SetText(value = self.default_country) 2288 2289 if self.default_region is not None: 2290 self._PRW_region.SetText(value = self.default_region)
2291 #----------------------------------------------------------------
2292 - def __perhaps_invalidate_address_searcher(self, ctrl=None, field=None):
2293 2294 adr = self._PRW_address_searcher.get_address() 2295 if adr is None: 2296 return True 2297 2298 if ctrl.GetValue().strip() != adr[field]: 2299 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None) 2300 return True 2301 2302 return False
2303 #----------------------------------------------------------------
2305 adr = self._PRW_address_searcher.get_address() 2306 if adr is None: 2307 return True 2308 2309 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode']) 2310 2311 self._PRW_street.SetText(value = adr['street'], data = adr['street']) 2312 self._PRW_street.set_context(context = u'zip', val = adr['postcode']) 2313 2314 self._TCTRL_number.SetValue(adr['number']) 2315 2316 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb']) 2317 self._PRW_urb.set_context(context = u'zip', val = adr['postcode']) 2318 2319 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state']) 2320 self._PRW_region.set_context(context = u'zip', val = adr['postcode']) 2321 2322 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country']) 2323 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
2324 #----------------------------------------------------------------
2325 - def __identity_valid_for_save(self):
2326 error = False 2327 2328 # name fields 2329 if self._PRW_lastname.GetValue().strip() == u'': 2330 error = True 2331 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.')) 2332 self._PRW_lastname.display_as_valid(False) 2333 else: 2334 self._PRW_lastname.display_as_valid(True) 2335 2336 if self._PRW_firstnames.GetValue().strip() == '': 2337 error = True 2338 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.')) 2339 self._PRW_firstnames.display_as_valid(False) 2340 else: 2341 self._PRW_firstnames.display_as_valid(True) 2342 2343 # gender 2344 if self._PRW_gender.GetData() is None: 2345 error = True 2346 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.')) 2347 self._PRW_gender.display_as_valid(False) 2348 else: 2349 self._PRW_gender.display_as_valid(True) 2350 2351 # dob validation 2352 if not self._DP_dob.is_valid_timestamp(): 2353 2354 gmDispatcher.send(signal = 'statustext', msg = _('Cannot use this date of birth. Does it lie before 1900 ?')) 2355 2356 do_it_anyway = gmGuiHelpers.gm_show_question ( 2357 _( 2358 'Are you sure you want to register this person\n' 2359 'without a valid date of birth ?\n' 2360 '\n' 2361 'This can be useful for temporary staff members\n' 2362 'but will provoke nag screens if this person\n' 2363 'becomes a patient.\n' 2364 '\n' 2365 'Note that the date of birth cannot technically\n' 2366 'be before 1900, either :-(\n' 2367 ), 2368 _('Registering new person') 2369 ) 2370 2371 if not do_it_anyway: 2372 error = True 2373 2374 if self._DP_dob.GetValue() is None: 2375 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 2376 elif self._DP_dob.GetValue().GetYear() < 1900: 2377 error = True 2378 gmDispatcher.send(signal = 'statustext', msg = _('The year of birth must lie after 1900.'), beep = True) 2379 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 2380 self._DP_dob.SetFocus() 2381 else: 2382 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 2383 self._DP_dob.Refresh() 2384 2385 # TOB validation if non-empty 2386 # if self._TCTRL_tob.GetValue().strip() != u'': 2387 2388 return (not error)
2389 #----------------------------------------------------------------
2390 - def __address_valid_for_save(self, empty_address_is_valid=False):
2391 2392 # existing address ? if so set other fields 2393 if self._PRW_address_searcher.GetData() is not None: 2394 wx.CallAfter(self.__set_fields_from_address_searcher) 2395 return True 2396 2397 # must either all contain something or none of them 2398 fields_to_fill = ( 2399 self._TCTRL_number, 2400 self._PRW_zip, 2401 self._PRW_street, 2402 self._PRW_urb, 2403 self._PRW_region, 2404 self._PRW_country 2405 ) 2406 no_of_filled_fields = 0 2407 2408 for field in fields_to_fill: 2409 if field.GetValue().strip() != u'': 2410 no_of_filled_fields += 1 2411 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 2412 field.Refresh() 2413 2414 # empty address ? 2415 if no_of_filled_fields == 0: 2416 if empty_address_is_valid: 2417 return True 2418 else: 2419 return None 2420 2421 # incompletely filled address ? 2422 if no_of_filled_fields != len(fields_to_fill): 2423 for field in fields_to_fill: 2424 if field.GetValue().strip() == u'': 2425 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 2426 field.SetFocus() 2427 field.Refresh() 2428 msg = _('To properly create an address, all the related fields must be filled in.') 2429 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 2430 return False 2431 2432 # fields which must contain a selected item 2433 # FIXME: they must also contain an *acceptable combination* which 2434 # FIXME: can only be tested against the database itself ... 2435 strict_fields = ( 2436 self._PRW_region, 2437 self._PRW_country 2438 ) 2439 error = False 2440 for field in strict_fields: 2441 if field.GetData() is None: 2442 error = True 2443 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 2444 field.SetFocus() 2445 else: 2446 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 2447 field.Refresh() 2448 2449 if error: 2450 msg = _('This field must contain an item selected from the dropdown list.') 2451 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 2452 return False 2453 2454 return True
2455 #----------------------------------------------------------------
2456 - def __register_interests(self):
2457 2458 # identity 2459 self._PRW_firstnames.add_callback_on_lose_focus(self._on_leaving_firstname) 2460 2461 # address 2462 self._PRW_address_searcher.add_callback_on_lose_focus(self._on_leaving_adress_searcher) 2463 2464 # invalidate address searcher when any field edited 2465 self._PRW_street.add_callback_on_lose_focus(self._invalidate_address_searcher) 2466 wx.EVT_KILL_FOCUS(self._TCTRL_number, self._invalidate_address_searcher) 2467 self._PRW_urb.add_callback_on_lose_focus(self._invalidate_address_searcher) 2468 self._PRW_region.add_callback_on_lose_focus(self._invalidate_address_searcher) 2469 2470 self._PRW_zip.add_callback_on_lose_focus(self._on_leaving_zip) 2471 self._PRW_country.add_callback_on_lose_focus(self._on_leaving_country)
2472 #---------------------------------------------------------------- 2473 # event handlers 2474 #----------------------------------------------------------------
2475 - def _on_leaving_firstname(self):
2476 """Set the gender according to entered firstname. 2477 2478 Matches are fetched from existing records in backend. 2479 """ 2480 # only set if not already set so as to not 2481 # overwrite a change by the user 2482 if self._PRW_gender.GetData() is not None: 2483 return True 2484 2485 firstname = self._PRW_firstnames.GetValue().strip() 2486 if firstname == u'': 2487 return True 2488 2489 gender = gmPerson.map_firstnames2gender(firstnames = firstname) 2490 if gender is None: 2491 return True 2492 2493 wx.CallAfter(self._PRW_gender.SetData, gender) 2494 return True
2495 #----------------------------------------------------------------
2496 - def _on_leaving_zip(self):
2497 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode') 2498 2499 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'') 2500 self._PRW_street.set_context(context = u'zip', val = zip_code) 2501 self._PRW_urb.set_context(context = u'zip', val = zip_code) 2502 self._PRW_region.set_context(context = u'zip', val = zip_code) 2503 self._PRW_country.set_context(context = u'zip', val = zip_code) 2504 2505 return True
2506 #----------------------------------------------------------------
2507 - def _on_leaving_country(self):
2508 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country') 2509 2510 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'') 2511 self._PRW_region.set_context(context = u'country', val = country) 2512 2513 return True
2514 #----------------------------------------------------------------
2515 - def _invalidate_address_searcher(self, *args, **kwargs):
2516 mapping = [ 2517 (self._PRW_street, 'street'), 2518 (self._TCTRL_number, 'number'), 2519 (self._PRW_urb, 'urb'), 2520 (self._PRW_region, 'l10n_state') 2521 ] 2522 2523 # loop through fields and invalidate address searcher if different 2524 for ctrl, field in mapping: 2525 if self.__perhaps_invalidate_address_searcher(ctrl, field): 2526 return True 2527 2528 return True
2529 #----------------------------------------------------------------
2531 adr = self._PRW_address_searcher.get_address() 2532 if adr is None: 2533 return True 2534 2535 wx.CallAfter(self.__set_fields_from_address_searcher) 2536 return True
2537 #---------------------------------------------------------------- 2538 # generic Edit Area mixin API 2539 #----------------------------------------------------------------
2540 - def _valid_for_save(self):
2541 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
2542 #----------------------------------------------------------------
2543 - def _save_as_new(self):
2544 2545 # identity 2546 new_identity = gmPerson.create_identity ( 2547 gender = self._PRW_gender.GetData(), 2548 dob = self._DP_dob.get_pydt(), 2549 lastnames = self._PRW_lastname.GetValue().strip(), 2550 firstnames = self._PRW_firstnames.GetValue().strip() 2551 ) 2552 _log.debug('identity created: %s' % new_identity) 2553 2554 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip()) 2555 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u'')) 2556 #TOB 2557 new_identity.save() 2558 2559 name = new_identity.get_active_name() 2560 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 2561 name.save() 2562 2563 # address 2564 is_valid = self.__address_valid_for_save(empty_address_is_valid = False) 2565 if is_valid is True: 2566 # because we currently only check for non-emptiness 2567 # we must still deal with database errors 2568 try: 2569 new_identity.link_address ( 2570 number = self._TCTRL_number.GetValue().strip(), 2571 street = self._PRW_street.GetValue().strip(), 2572 postcode = self._PRW_zip.GetValue().strip(), 2573 urb = self._PRW_urb.GetValue().strip(), 2574 state = self._PRW_region.GetData(), 2575 country = self._PRW_country.GetData() 2576 ) 2577 except psycopg2.InternalError: 2578 #except StandardError: 2579 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip()) 2580 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip()) 2581 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip()) 2582 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip()) 2583 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip()) 2584 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip()) 2585 _log.exception('cannot link address') 2586 gmGuiHelpers.gm_show_error ( 2587 aTitle = _('Saving address'), 2588 aMessage = _( 2589 'Cannot save this address.\n' 2590 '\n' 2591 'You will have to add it via the Demographics plugin.\n' 2592 ) 2593 ) 2594 elif is_valid is False: 2595 gmGuiHelpers.gm_show_error ( 2596 aTitle = _('Saving address'), 2597 aMessage = _( 2598 'Address not saved.\n' 2599 '\n' 2600 'You will have to add it via the Demographics plugin.\n' 2601 ) 2602 ) 2603 # else it is None which means empty address which we ignore 2604 2605 # phone 2606 new_identity.link_comm_channel ( 2607 comm_medium = u'homephone', 2608 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''), 2609 is_confidential = False 2610 ) 2611 2612 # external ID 2613 pk_type = self._PRW_external_id_type.GetData() 2614 id_value = self._TCTRL_external_id_value.GetValue().strip() 2615 if (pk_type is not None) and (id_value != u''): 2616 new_identity.add_external_id(value = id_value, pk_type = pk_type) 2617 2618 # occupation 2619 new_identity.link_occupation ( 2620 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'') 2621 ) 2622 2623 self.data = new_identity 2624 return True
2625 #----------------------------------------------------------------
2626 - def _save_as_update(self):
2627 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2628 #----------------------------------------------------------------
2629 - def _refresh_as_new(self):
2630 # FIXME: button "empty out" 2631 return
2632 #----------------------------------------------------------------
2633 - def _refresh_from_existing(self):
2634 return # there is no forward button so nothing to do here
2635 #----------------------------------------------------------------
2637 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2638 #============================================================ 2639 # new-patient wizard classes 2640 #============================================================
2641 -class cBasicPatDetailsPage(wx.wizard.WizardPageSimple):
2642 """ 2643 Wizard page for entering patient's basic demographic information 2644 """ 2645 2646 form_fields = ( 2647 'firstnames', 'lastnames', 'nick', 'dob', 'gender', 'title', 'occupation', 2648 'address_number', 'zip_code', 'street', 'town', 'state', 'country', 'phone', 'comment' 2649 ) 2650
2651 - def __init__(self, parent, title):
2652 """ 2653 Creates a new instance of BasicPatDetailsPage 2654 @param parent - The parent widget 2655 @type parent - A wx.Window instance 2656 @param tile - The title of the page 2657 @type title - A StringType instance 2658 """ 2659 wx.wizard.WizardPageSimple.__init__(self, parent) #, bitmap = gmGuiHelpers.gm_icon(_('oneperson')) 2660 self.__title = title 2661 self.__do_layout() 2662 self.__register_interests()
2663 #--------------------------------------------------------
2664 - def __do_layout(self):
2665 PNL_form = wx.Panel(self, -1) 2666 2667 # last name 2668 STT_lastname = wx.StaticText(PNL_form, -1, _('Last name')) 2669 STT_lastname.SetForegroundColour('red') 2670 self.PRW_lastname = cLastnamePhraseWheel(parent = PNL_form, id = -1) 2671 self.PRW_lastname.SetToolTipString(_('Required: lastname (family name)')) 2672 2673 # first name 2674 STT_firstname = wx.StaticText(PNL_form, -1, _('First name(s)')) 2675 STT_firstname.SetForegroundColour('red') 2676 self.PRW_firstname = cFirstnamePhraseWheel(parent = PNL_form, id = -1) 2677 self.PRW_firstname.SetToolTipString(_('Required: surname/given name/first name')) 2678 2679 # nickname 2680 STT_nick = wx.StaticText(PNL_form, -1, _('Nick name')) 2681 self.PRW_nick = cNicknamePhraseWheel(parent = PNL_form, id = -1) 2682 2683 # DOB 2684 STT_dob = wx.StaticText(PNL_form, -1, _('Date of birth')) 2685 STT_dob.SetForegroundColour('red') 2686 self.PRW_dob = gmDateTimeInput.cFuzzyTimestampInput(parent = PNL_form, id = -1) 2687 self.PRW_dob.SetToolTipString(_("Required: date of birth, if unknown or aliasing wanted then invent one")) 2688 2689 # gender 2690 STT_gender = wx.StaticText(PNL_form, -1, _('Gender')) 2691 STT_gender.SetForegroundColour('red') 2692 self.PRW_gender = cGenderSelectionPhraseWheel(parent = PNL_form, id=-1) 2693 self.PRW_gender.SetToolTipString(_("Required: gender of patient")) 2694 2695 # title 2696 STT_title = wx.StaticText(PNL_form, -1, _('Title')) 2697 self.PRW_title = cTitlePhraseWheel(parent = PNL_form, id = -1) 2698 2699 # zip code 2700 STT_zip_code = wx.StaticText(PNL_form, -1, _('Postal code')) 2701 STT_zip_code.SetForegroundColour('orange') 2702 self.PRW_zip_code = cZipcodePhraseWheel(parent = PNL_form, id = -1) 2703 self.PRW_zip_code.SetToolTipString(_("primary/home address: zip/postal code")) 2704 2705 # street 2706 STT_street = wx.StaticText(PNL_form, -1, _('Street')) 2707 STT_street.SetForegroundColour('orange') 2708 self.PRW_street = cStreetPhraseWheel(parent = PNL_form, id = -1) 2709 self.PRW_street.SetToolTipString(_("primary/home address: name of street")) 2710 2711 # address number 2712 STT_address_number = wx.StaticText(PNL_form, -1, _('Number')) 2713 STT_address_number.SetForegroundColour('orange') 2714 self.TTC_address_number = wx.TextCtrl(PNL_form, -1) 2715 self.TTC_address_number.SetToolTipString(_("primary/home address: address number")) 2716 2717 # town 2718 STT_town = wx.StaticText(PNL_form, -1, _('Place')) 2719 STT_town.SetForegroundColour('orange') 2720 self.PRW_town = cUrbPhraseWheel(parent = PNL_form, id = -1) 2721 self.PRW_town.SetToolTipString(_("primary/home address: city/town/village/dwelling/...")) 2722 2723 # state 2724 STT_state = wx.StaticText(PNL_form, -1, _('Region')) 2725 STT_state.SetForegroundColour('orange') 2726 self.PRW_state = cStateSelectionPhraseWheel(parent=PNL_form, id=-1) 2727 self.PRW_state.SetToolTipString(_("primary/home address: state/province/county/...")) 2728 2729 # country 2730 STT_country = wx.StaticText(PNL_form, -1, _('Country')) 2731 STT_country.SetForegroundColour('orange') 2732 self.PRW_country = cCountryPhraseWheel(parent = PNL_form, id = -1) 2733 self.PRW_country.SetToolTipString(_("primary/home address: country")) 2734 2735 # phone 2736 STT_phone = wx.StaticText(PNL_form, -1, _('Phone')) 2737 self.TTC_phone = wx.TextCtrl(PNL_form, -1) 2738 self.TTC_phone.SetToolTipString(_("phone number at home")) 2739 2740 # occupation 2741 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation')) 2742 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1) 2743 2744 # comment 2745 STT_comment = wx.StaticText(PNL_form, -1, _('Comment')) 2746 self.TCTRL_comment = wx.TextCtrl(PNL_form, -1) 2747 self.TCTRL_comment.SetToolTipString(_('A comment on this patient.')) 2748 2749 # form main validator 2750 self.form_DTD = cFormDTD(fields = self.__class__.form_fields) 2751 PNL_form.SetValidator(cBasicPatDetailsPageValidator(dtd = self.form_DTD)) 2752 2753 # layout input widgets 2754 SZR_input = wx.FlexGridSizer(cols = 2, rows = 16, vgap = 4, hgap = 4) 2755 SZR_input.AddGrowableCol(1) 2756 SZR_input.Add(STT_lastname, 0, wx.SHAPED) 2757 SZR_input.Add(self.PRW_lastname, 1, wx.EXPAND) 2758 SZR_input.Add(STT_firstname, 0, wx.SHAPED) 2759 SZR_input.Add(self.PRW_firstname, 1, wx.EXPAND) 2760 SZR_input.Add(STT_nick, 0, wx.SHAPED) 2761 SZR_input.Add(self.PRW_nick, 1, wx.EXPAND) 2762 SZR_input.Add(STT_dob, 0, wx.SHAPED) 2763 SZR_input.Add(self.PRW_dob, 1, wx.EXPAND) 2764 SZR_input.Add(STT_gender, 0, wx.SHAPED) 2765 SZR_input.Add(self.PRW_gender, 1, wx.EXPAND) 2766 SZR_input.Add(STT_title, 0, wx.SHAPED) 2767 SZR_input.Add(self.PRW_title, 1, wx.EXPAND) 2768 SZR_input.Add(STT_zip_code, 0, wx.SHAPED) 2769 SZR_input.Add(self.PRW_zip_code, 1, wx.EXPAND) 2770 SZR_input.Add(STT_street, 0, wx.SHAPED) 2771 SZR_input.Add(self.PRW_street, 1, wx.EXPAND) 2772 SZR_input.Add(STT_address_number, 0, wx.SHAPED) 2773 SZR_input.Add(self.TTC_address_number, 1, wx.EXPAND) 2774 SZR_input.Add(STT_town, 0, wx.SHAPED) 2775 SZR_input.Add(self.PRW_town, 1, wx.EXPAND) 2776 SZR_input.Add(STT_state, 0, wx.SHAPED) 2777 SZR_input.Add(self.PRW_state, 1, wx.EXPAND) 2778 SZR_input.Add(STT_country, 0, wx.SHAPED) 2779 SZR_input.Add(self.PRW_country, 1, wx.EXPAND) 2780 SZR_input.Add(STT_phone, 0, wx.SHAPED) 2781 SZR_input.Add(self.TTC_phone, 1, wx.EXPAND) 2782 SZR_input.Add(STT_occupation, 0, wx.SHAPED) 2783 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND) 2784 SZR_input.Add(STT_comment, 0, wx.SHAPED) 2785 SZR_input.Add(self.TCTRL_comment, 1, wx.EXPAND) 2786 2787 PNL_form.SetSizerAndFit(SZR_input) 2788 2789 # layout page 2790 SZR_main = gmGuiHelpers.makePageTitle(self, self.__title) 2791 SZR_main.Add(PNL_form, 1, wx.EXPAND)
2792 #-------------------------------------------------------- 2793 # event handling 2794 #--------------------------------------------------------
2795 - def __register_interests(self):
2796 self.PRW_firstname.add_callback_on_lose_focus(self.on_name_set) 2797 self.PRW_country.add_callback_on_selection(self.on_country_selected) 2798 self.PRW_zip_code.add_callback_on_lose_focus(self.on_zip_set)
2799 #--------------------------------------------------------
2800 - def on_country_selected(self, data):
2801 """Set the states according to entered country.""" 2802 self.PRW_state.set_context(context=u'country', val=data) 2803 return True
2804 #--------------------------------------------------------
2805 - def on_name_set(self):
2806 """Set the gender according to entered firstname. 2807 2808 Matches are fetched from existing records in backend. 2809 """ 2810 firstname = self.PRW_firstname.GetValue().strip() 2811 rows, idx = gmPG2.run_ro_queries(queries = [{ 2812 'cmd': u"select gender from dem.name_gender_map where name ilike %s", 2813 'args': [firstname] 2814 }]) 2815 if len(rows) == 0: 2816 return True 2817 wx.CallAfter(self.PRW_gender.SetData, rows[0][0]) 2818 return True
2819 #--------------------------------------------------------
2820 - def on_zip_set(self):
2821 """Set the street, town, state and country according to entered zip code.""" 2822 zip_code = self.PRW_zip_code.GetValue().strip() 2823 self.PRW_street.set_context(context=u'zip', val=zip_code) 2824 self.PRW_town.set_context(context=u'zip', val=zip_code) 2825 self.PRW_state.set_context(context=u'zip', val=zip_code) 2826 self.PRW_country.set_context(context=u'zip', val=zip_code) 2827 return True
2828 #============================================================
2829 -class cNewPatientWizard(wx.wizard.Wizard):
2830 """ 2831 Wizard to create a new patient. 2832 2833 TODO: 2834 - write pages for different "themes" of patient creation 2835 - make it configurable which pages are loaded 2836 - make available sets of pages that apply to a country 2837 - make loading of some pages depend upon values in earlier pages, eg 2838 when the patient is female and older than 13 include a page about 2839 "female" data (number of kids etc) 2840 2841 FIXME: use: wizard.FindWindowById(wx.ID_FORWARD).Disable() 2842 """ 2843 #--------------------------------------------------------
2844 - def __init__(self, parent, title = _('Register new person'), subtitle = _('Basic demographic details') ):
2845 """ 2846 Creates a new instance of NewPatientWizard 2847 @param parent - The parent widget 2848 @type parent - A wx.Window instance 2849 """ 2850 id_wiz = wx.NewId() 2851 wx.wizard.Wizard.__init__(self, parent, id_wiz, title) #images.getWizTest1Bitmap() 2852 self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY) 2853 self.__subtitle = subtitle 2854 self.__do_layout()
2855 #--------------------------------------------------------
2856 - def RunWizard(self, activate=False):
2857 """Create new patient. 2858 2859 activate, too, if told to do so (and patient successfully created) 2860 """ 2861 while True: 2862 2863 if not wx.wizard.Wizard.RunWizard(self, self.basic_pat_details): 2864 return False 2865 2866 try: 2867 # retrieve DTD and create patient 2868 ident = create_identity_from_dtd(dtd = self.basic_pat_details.form_DTD) 2869 except: 2870 _log.exception('cannot add new patient - missing identity fields') 2871 gmGuiHelpers.gm_show_error ( 2872 _('Cannot create new patient.\n' 2873 'Missing parts of the identity.' 2874 ), 2875 _('Adding new patient') 2876 ) 2877 continue 2878 2879 update_identity_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 2880 2881 try: 2882 link_contacts_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 2883 except: 2884 _log.exception('cannot finalize new patient - missing address fields') 2885 gmGuiHelpers.gm_show_error ( 2886 _('Cannot add address for the new patient.\n' 2887 'You must either enter all of the address fields or\n' 2888 'none at all. The relevant fields are marked in yellow.\n' 2889 '\n' 2890 'You will need to add the address details in the\n' 2891 'demographics module.' 2892 ), 2893 _('Adding new patient') 2894 ) 2895 break 2896 2897 link_occupation_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 2898 2899 break 2900 2901 if activate: 2902 from Gnumed.wxpython import gmPatSearchWidgets 2903 gmPatSearchWidgets.set_active_patient(patient = ident) 2904 2905 return ident
2906 #-------------------------------------------------------- 2907 # internal helpers 2908 #--------------------------------------------------------
2909 - def __do_layout(self):
2910 """Arrange widgets. 2911 """ 2912 # Create the wizard pages 2913 self.basic_pat_details = cBasicPatDetailsPage(self, self.__subtitle ) 2914 self.FitToPage(self.basic_pat_details)
2915 #============================================================ 2916 #============================================================
2917 -class cBasicPatDetailsPageValidator(wx.PyValidator):
2918 """ 2919 This validator is used to ensure that the user has entered all 2920 the required conditional values in the page (eg., to properly 2921 create an address, all the related fields must be filled). 2922 """ 2923 #--------------------------------------------------------
2924 - def __init__(self, dtd):
2925 """ 2926 Validator initialization. 2927 @param dtd The object containing the data model. 2928 @type dtd A cFormDTD instance 2929 """ 2930 # initialize parent class 2931 wx.PyValidator.__init__(self) 2932 # validator's storage object 2933 self.form_DTD = dtd
2934 #--------------------------------------------------------
2935 - def Clone(self):
2936 """ 2937 Standard cloner. 2938 Note that every validator must implement the Clone() method. 2939 """ 2940 return cBasicPatDetailsPageValidator(dtd = self.form_DTD) # FIXME: probably need new instance of DTD ?
2941 #--------------------------------------------------------
2942 - def Validate(self, parent = None):
2943 """ 2944 Validate the contents of the given text control. 2945 """ 2946 _pnl_form = self.GetWindow().GetParent() 2947 2948 error = False 2949 2950 # name fields 2951 if _pnl_form.PRW_lastname.GetValue().strip() == '': 2952 error = True 2953 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.')) 2954 _pnl_form.PRW_lastname.SetBackgroundColour('pink') 2955 _pnl_form.PRW_lastname.Refresh() 2956 else: 2957 _pnl_form.PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2958 _pnl_form.PRW_lastname.Refresh() 2959 2960 if _pnl_form.PRW_firstname.GetValue().strip() == '': 2961 error = True 2962 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.')) 2963 _pnl_form.PRW_firstname.SetBackgroundColour('pink') 2964 _pnl_form.PRW_firstname.Refresh() 2965 else: 2966 _pnl_form.PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2967 _pnl_form.PRW_firstname.Refresh() 2968 2969 # gender 2970 if _pnl_form.PRW_gender.GetData() is None: 2971 error = True 2972 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.')) 2973 _pnl_form.PRW_gender.SetBackgroundColour('pink') 2974 _pnl_form.PRW_gender.Refresh() 2975 else: 2976 _pnl_form.PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2977 _pnl_form.PRW_gender.Refresh() 2978 2979 # dob validation 2980 if ( 2981 (_pnl_form.PRW_dob.GetValue().strip() == u'') 2982 or (not _pnl_form.PRW_dob.is_valid_timestamp()) 2983 or (_pnl_form.PRW_dob.GetData().timestamp.year < 1900) 2984 ): 2985 error = True 2986 msg = _('Cannot parse <%s> into proper timestamp.') % _pnl_form.PRW_dob.GetValue() 2987 gmDispatcher.send(signal = 'statustext', msg = msg) 2988 _pnl_form.PRW_dob.SetBackgroundColour('pink') 2989 _pnl_form.PRW_dob.Refresh() 2990 else: 2991 _pnl_form.PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2992 _pnl_form.PRW_dob.Refresh() 2993 2994 # address 2995 is_any_field_filled = False 2996 address_fields = ( 2997 _pnl_form.TTC_address_number, 2998 _pnl_form.PRW_zip_code, 2999 _pnl_form.PRW_street, 3000 _pnl_form.PRW_town 3001 ) 3002 for field in address_fields: 3003 if field.GetValue().strip() == u'': 3004 if is_any_field_filled: 3005 error = True 3006 msg = _('To properly create an address, all the related fields must be filled in.') 3007 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 3008 field.SetBackgroundColour('pink') 3009 field.SetFocus() 3010 field.Refresh() 3011 else: 3012 is_any_field_filled = True 3013 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 3014 field.Refresh() 3015 3016 address_fields = ( 3017 _pnl_form.PRW_state, 3018 _pnl_form.PRW_country 3019 ) 3020 for field in address_fields: 3021 if field.GetData() is None: 3022 if is_any_field_filled: 3023 error = True 3024 msg = _('To properly create an address, all the related fields must be filled in.') 3025 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 3026 field.SetBackgroundColour('pink') 3027 field.SetFocus() 3028 field.Refresh() 3029 else: 3030 is_any_field_filled = True 3031 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 3032 field.Refresh() 3033 3034 return (not error)
3035 #--------------------------------------------------------
3036 - def TransferToWindow(self):
3037 """ 3038 Transfer data from validator to window. 3039 The default implementation returns False, indicating that an error 3040 occurred. We simply return True, as we don't do any data transfer. 3041 """ 3042 _pnl_form = self.GetWindow().GetParent() 3043 # fill in controls with values from self.form_DTD 3044 _pnl_form.PRW_gender.SetData(self.form_DTD['gender']) 3045 _pnl_form.PRW_dob.SetText(self.form_DTD['dob']) 3046 _pnl_form.PRW_lastname.SetText(self.form_DTD['lastnames']) 3047 _pnl_form.PRW_firstname.SetText(self.form_DTD['firstnames']) 3048 _pnl_form.PRW_title.SetText(self.form_DTD['title']) 3049 _pnl_form.PRW_nick.SetText(self.form_DTD['nick']) 3050 _pnl_form.PRW_occupation.SetText(self.form_DTD['occupation']) 3051 _pnl_form.TTC_address_number.SetValue(self.form_DTD['address_number']) 3052 _pnl_form.PRW_street.SetText(self.form_DTD['street']) 3053 _pnl_form.PRW_zip_code.SetText(self.form_DTD['zip_code']) 3054 _pnl_form.PRW_town.SetText(self.form_DTD['town']) 3055 _pnl_form.PRW_state.SetData(self.form_DTD['state']) 3056 _pnl_form.PRW_country.SetData(self.form_DTD['country']) 3057 _pnl_form.TTC_phone.SetValue(self.form_DTD['phone']) 3058 _pnl_form.TCTRL_comment.SetValue(self.form_DTD['comment']) 3059 return True # Prevent wxDialog from complaining
3060 #--------------------------------------------------------
3061 - def TransferFromWindow(self):
3062 """ 3063 Transfer data from window to validator. 3064 The default implementation returns False, indicating that an error 3065 occurred. We simply return True, as we don't do any data transfer. 3066 """ 3067 # FIXME: should be called automatically 3068 if not self.GetWindow().GetParent().Validate(): 3069 return False 3070 try: 3071 _pnl_form = self.GetWindow().GetParent() 3072 # fill in self.form_DTD with values from controls 3073 self.form_DTD['gender'] = _pnl_form.PRW_gender.GetData() 3074 self.form_DTD['dob'] = _pnl_form.PRW_dob.GetData() 3075 3076 self.form_DTD['lastnames'] = _pnl_form.PRW_lastname.GetValue() 3077 self.form_DTD['firstnames'] = _pnl_form.PRW_firstname.GetValue() 3078 self.form_DTD['title'] = _pnl_form.PRW_title.GetValue() 3079 self.form_DTD['nick'] = _pnl_form.PRW_nick.GetValue() 3080 3081 self.form_DTD['occupation'] = _pnl_form.PRW_occupation.GetValue() 3082 3083 self.form_DTD['address_number'] = _pnl_form.TTC_address_number.GetValue() 3084 self.form_DTD['street'] = _pnl_form.PRW_street.GetValue() 3085 self.form_DTD['zip_code'] = _pnl_form.PRW_zip_code.GetValue() 3086 self.form_DTD['town'] = _pnl_form.PRW_town.GetValue() 3087 self.form_DTD['state'] = _pnl_form.PRW_state.GetData() 3088 self.form_DTD['country'] = _pnl_form.PRW_country.GetData() 3089 3090 self.form_DTD['phone'] = _pnl_form.TTC_phone.GetValue() 3091 3092 self.form_DTD['comment'] = _pnl_form.TCTRL_comment.GetValue() 3093 except: 3094 return False 3095 return True
3096 #============================================================ 3097 #class cFormDTD: 3098 # """ 3099 # Simple Data Transfer Dictionary class to make easy the trasfer of 3100 # data between the form (view) and the business logic. 3101 # 3102 # Maybe later consider turning this into a standard dict by 3103 # {}.fromkeys([key, key, ...], default) when it becomes clear that 3104 # we really don't need the added potential of a full-fledged class. 3105 # """ 3106 # def __init__(self, fields): 3107 # """ 3108 # Initialize the DTD with the supplied field names. 3109 # @param fields The names of the fields. 3110 # @type fields A TupleType instance. 3111 # """ 3112 # self.data = {} 3113 # for a_field in fields: 3114 # self.data[a_field] = '' 3115 # 3116 # def __getitem__(self, attribute): 3117 # """ 3118 # Retrieve the value of the given attribute (key) 3119 # @param attribute The attribute (key) to retrieve its value for. 3120 # @type attribute a StringType instance. 3121 # """ 3122 # if not self.data[attribute]: 3123 # return '' 3124 # return self.data[attribute] 3125 # 3126 # def __setitem__(self, attribute, value): 3127 # """ 3128 # Set the value of a given attribute (key). 3129 # @param attribute The attribute (key) to set its value for. 3130 # @type attribute a StringType instance. 3131 # @param avaluee The value to set. 3132 # @rtpe attribute a StringType instance. 3133 # """ 3134 # self.data[attribute] = value 3135 # 3136 # def __str__(self): 3137 # """ 3138 # Print string representation of the DTD object. 3139 # """ 3140 # return str(self.data) 3141 #============================================================ 3142 # patient demographics editing classes 3143 #============================================================
3144 -class cPersonDemographicsEditorNb(wx.Notebook):
3145 """Notebook displaying demographics editing pages: 3146 3147 - Identity 3148 - Contacts (addresses, phone numbers, etc) 3149 - Social Network (significant others, GP, etc) 3150 3151 Does NOT act on/listen to the current patient. 3152 """ 3153 #--------------------------------------------------------
3154 - def __init__(self, parent, id):
3155 3156 wx.Notebook.__init__ ( 3157 self, 3158 parent = parent, 3159 id = id, 3160 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER, 3161 name = self.__class__.__name__ 3162 ) 3163 3164 self.__identity = None 3165 self.__do_layout() 3166 self.SetSelection(0)
3167 #-------------------------------------------------------- 3168 # public API 3169 #--------------------------------------------------------
3170 - def refresh(self):
3171 """Populate fields in pages with data from model.""" 3172 for page_idx in range(self.GetPageCount()): 3173 page = self.GetPage(page_idx) 3174 page.identity = self.__identity 3175 3176 return True
3177 #-------------------------------------------------------- 3178 # internal API 3179 #--------------------------------------------------------
3180 - def __do_layout(self):
3181 """Build patient edition notebook pages.""" 3182 3183 # contacts page 3184 new_page = cPersonContactsManagerPnl(self, -1) 3185 new_page.identity = self.__identity 3186 self.AddPage ( 3187 page = new_page, 3188 text = _('Contacts'), 3189 select = True 3190 ) 3191 3192 # identity page 3193 new_page = cPersonIdentityManagerPnl(self, -1) 3194 new_page.identity = self.__identity 3195 self.AddPage ( 3196 page = new_page, 3197 text = _('Identity'), 3198 select = False 3199 ) 3200 3201 # social network page 3202 new_page = cPersonSocialNetworkManagerPnl(self, -1) 3203 new_page.identity = self.__identity 3204 self.AddPage ( 3205 page = new_page, 3206 text = _('Social Network'), 3207 select = False 3208 )
3209 #-------------------------------------------------------- 3210 # properties 3211 #--------------------------------------------------------
3212 - def _get_identity(self):
3213 return self.__identity
3214
3215 - def _set_identity(self, identity):
3216 self.__identity = identity
3217 3218 identity = property(_get_identity, _set_identity)
3219 #============================================================ 3220 #============================================================ 3221 # FIXME: support multiple occupations 3222 # FIXME: redo with wxGlade 3223
3224 -class cPatOccupationsPanel(wx.Panel):
3225 """Page containing patient occupations edition fields. 3226 """
3227 - def __init__(self, parent, id, ident=None):
3228 """ 3229 Creates a new instance of BasicPatDetailsPage 3230 @param parent - The parent widget 3231 @type parent - A wx.Window instance 3232 @param id - The widget id 3233 @type id - An integer 3234 """ 3235 wx.Panel.__init__(self, parent, id) 3236 self.__ident = ident 3237 self.__do_layout()
3238 #--------------------------------------------------------
3239 - def __do_layout(self):
3240 PNL_form = wx.Panel(self, -1) 3241 # occupation 3242 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation')) 3243 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1) 3244 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient")) 3245 # known since 3246 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated')) 3247 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY) 3248 3249 # layout input widgets 3250 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4) 3251 SZR_input.AddGrowableCol(1) 3252 SZR_input.Add(STT_occupation, 0, wx.SHAPED) 3253 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND) 3254 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED) 3255 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND) 3256 PNL_form.SetSizerAndFit(SZR_input) 3257 3258 # layout page 3259 SZR_main = wx.BoxSizer(wx.VERTICAL) 3260 SZR_main.Add(PNL_form, 1, wx.EXPAND) 3261 self.SetSizer(SZR_main)
3262 #--------------------------------------------------------
3263 - def set_identity(self, identity):
3264 return self.refresh(identity=identity)
3265 #--------------------------------------------------------
3266 - def refresh(self, identity=None):
3267 if identity is not None: 3268 self.__ident = identity 3269 jobs = self.__ident.get_occupations() 3270 if len(jobs) > 0: 3271 self.PRW_occupation.SetText(jobs[0]['l10n_occupation']) 3272 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y')) 3273 return True
3274 #--------------------------------------------------------
3275 - def save(self):
3276 if self.PRW_occupation.IsModified(): 3277 new_job = self.PRW_occupation.GetValue().strip() 3278 jobs = self.__ident.get_occupations() 3279 for job in jobs: 3280 if job['l10n_occupation'] == new_job: 3281 continue 3282 self.__ident.unlink_occupation(occupation = job['l10n_occupation']) 3283 self.__ident.link_occupation(occupation = new_job) 3284 return True
3285 #============================================================
3286 -class cNotebookedPatEditionPanel(wx.Panel, gmRegetMixin.cRegetOnPaintMixin):
3287 """Patient demographics plugin for main notebook. 3288 3289 Hosts another notebook with pages for Identity, Contacts, etc. 3290 3291 Acts on/listens to the currently active patient. 3292 """ 3293 #--------------------------------------------------------
3294 - def __init__(self, parent, id):
3295 wx.Panel.__init__ (self, parent = parent, id = id, style = wx.NO_BORDER) 3296 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 3297 self.__do_layout() 3298 self.__register_interests()
3299 #-------------------------------------------------------- 3300 # public API 3301 #-------------------------------------------------------- 3302 #-------------------------------------------------------- 3303 # internal helpers 3304 #--------------------------------------------------------
3305 - def __do_layout(self):
3306 """Arrange widgets.""" 3307 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1) 3308 3309 szr_main = wx.BoxSizer(wx.VERTICAL) 3310 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND) 3311 self.SetSizerAndFit(szr_main)
3312 #-------------------------------------------------------- 3313 # event handling 3314 #--------------------------------------------------------
3315 - def __register_interests(self):
3316 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 3317 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
3318 #--------------------------------------------------------
3319 - def _on_pre_patient_selection(self):
3320 self._schedule_data_reget()
3321 #--------------------------------------------------------
3322 - def _on_post_patient_selection(self):
3323 self._schedule_data_reget()
3324 #-------------------------------------------------------- 3325 # reget mixin API 3326 #--------------------------------------------------------
3327 - def _populate_with_data(self):
3328 """Populate fields in pages with data from model.""" 3329 pat = gmPerson.gmCurrentPatient() 3330 if pat.connected: 3331 self.__patient_notebook.identity = pat 3332 else: 3333 self.__patient_notebook.identity = None 3334 self.__patient_notebook.refresh() 3335 return True
3336 #============================================================ 3337 #def create_identity_from_dtd(dtd=None): 3338 # """ 3339 # Register a new patient, given the data supplied in the 3340 # Data Transfer Dictionary object. 3341 # 3342 # @param basic_details_DTD Data Transfer Dictionary encapsulating all the 3343 # supplied data. 3344 # @type basic_details_DTD A cFormDTD instance. 3345 # """ 3346 # new_identity = gmPerson.create_identity ( 3347 # gender = dtd['gender'], 3348 # dob = dtd['dob'].get_pydt(), 3349 # lastnames = dtd['lastnames'], 3350 # firstnames = dtd['firstnames'] 3351 # ) 3352 # if new_identity is None: 3353 # _log.error('cannot create identity from %s' % str(dtd)) 3354 # return None 3355 # _log.debug('identity created: %s' % new_identity) 3356 # 3357 # if dtd['comment'] is not None: 3358 # if dtd['comment'].strip() != u'': 3359 # name = new_identity.get_active_name() 3360 # name['comment'] = dtd['comment'] 3361 # name.save_payload() 3362 # 3363 # return new_identity 3364 #============================================================ 3365 #def update_identity_from_dtd(identity, dtd=None): 3366 # """ 3367 # Update patient details with data supplied by 3368 # Data Transfer Dictionary object. 3369 # 3370 # @param basic_details_DTD Data Transfer Dictionary encapsulating all the 3371 # supplied data. 3372 # @type basic_details_DTD A cFormDTD instance. 3373 # """ 3374 # # identity 3375 # if identity['gender'] != dtd['gender']: 3376 # identity['gender'] = dtd['gender'] 3377 # if identity['dob'] != dtd['dob'].get_pydt(): 3378 # identity['dob'] = dtd['dob'].get_pydt() 3379 # if len(dtd['title']) > 0 and identity['title'] != dtd['title']: 3380 # identity['title'] = dtd['title'] 3381 # # FIXME: error checking 3382 # # FIXME: we need a trigger to update the values of the 3383 # # view, identity['keys'], eg. lastnames and firstnames 3384 # # are not refreshed. 3385 # identity.save_payload() 3386 # 3387 # # names 3388 # # FIXME: proper handling of "active" 3389 # if identity['firstnames'] != dtd['firstnames'] or identity['lastnames'] != dtd['lastnames']: 3390 # new_name = identity.add_name(firstnames = dtd['firstnames'], lastnames = dtd['lastnames'], active = True) 3391 # # nickname 3392 # if len(dtd['nick']) > 0 and identity['preferred'] != dtd['nick']: 3393 # identity.set_nickname(nickname = dtd['nick']) 3394 # 3395 # return True 3396 #============================================================ 3397 #def link_contacts_from_dtd(identity, dtd=None): 3398 # """ 3399 # Update patient details with data supplied by 3400 # Data Transfer Dictionary object. 3401 # 3402 # @param basic_details_DTD Data Transfer Dictionary encapsulating all the 3403 # supplied data. 3404 # @type basic_details_DTD A cFormDTD instance. 3405 # """ 3406 # lng = len ( 3407 # dtd['address_number'].strip() + 3408 # dtd['street'].strip() + 3409 # dtd['zip_code'].strip() + 3410 # dtd['town'].strip() + 3411 # dtd['state'].strip() + 3412 # dtd['country'].strip() 3413 # ) 3414 # # FIXME: improve error checking 3415 # if lng > 5: 3416 # # FIXME: support address type 3417 # success = identity.link_address ( 3418 # number = dtd['address_number'].strip(), 3419 # street = dtd['street'].strip(), 3420 # postcode = dtd['zip_code'].strip(), 3421 # urb = dtd['town'].strip(), 3422 # state = dtd['state'].strip(), 3423 # country = dtd['country'].strip() 3424 # ) 3425 # if not success: 3426 # gmDispatcher.send(signal='statustext', msg = _('Cannot add patient address.')) 3427 # else: 3428 # gmDispatcher.send(signal='statustext', msg = _('Cannot add patient address. Missing fields.')) 3429 # 3430 # if len(dtd['phone']) > 0: 3431 # identity.link_comm_channel ( 3432 # comm_medium = 'homephone', 3433 # url = dtd['phone'], 3434 # is_confidential = False 3435 # ) 3436 # 3437 # # FIXME: error checking 3438 ## identity.save_payload() 3439 # return True 3440 #============================================================ 3441 #def link_occupation_from_dtd(identity, dtd=None): 3442 # """ 3443 # Update patient details with data supplied by 3444 # Data Transfer Dictionary object. 3445 # 3446 # @param basic_details_DTD Data Transfer Dictionary encapsulating all the 3447 # supplied data. 3448 # @type basic_details_DTD A cFormDTD instance. 3449 # """ 3450 # identity.link_occupation(occupation = dtd['occupation']) 3451 # 3452 # return True 3453 #============================================================
3454 -class TestWizardPanel(wx.Panel):
3455 """ 3456 Utility class to test the new patient wizard. 3457 """ 3458 #--------------------------------------------------------
3459 - def __init__(self, parent, id):
3460 """ 3461 Create a new instance of TestPanel. 3462 @param parent The parent widget 3463 @type parent A wx.Window instance 3464 """ 3465 wx.Panel.__init__(self, parent, id) 3466 wizard = cNewPatientWizard(self) 3467 print wizard.RunWizard()
3468 #============================================================ 3469 if __name__ == "__main__": 3470 3471 #--------------------------------------------------------
3472 - def test_zipcode_prw():
3473 app = wx.PyWidgetTester(size = (200, 50)) 3474 pw = cZipcodePhraseWheel(app.frame, -1) 3475 app.frame.Show(True) 3476 app.MainLoop()
3477 #--------------------------------------------------------
3478 - def test_state_prw():
3479 app = wx.PyWidgetTester(size = (200, 50)) 3480 pw = cStateSelectionPhraseWheel(app.frame, -1) 3481 # pw.set_context(context = u'zip', val = u'04318') 3482 # pw.set_context(context = u'country', val = u'Deutschland') 3483 app.frame.Show(True) 3484 app.MainLoop()
3485 #--------------------------------------------------------
3486 - def test_urb_prw():
3487 app = wx.PyWidgetTester(size = (200, 50)) 3488 pw = cUrbPhraseWheel(app.frame, -1) 3489 app.frame.Show(True) 3490 pw.set_context(context = u'zip', val = u'04317') 3491 app.MainLoop()
3492 #--------------------------------------------------------
3493 - def test_suburb_prw():
3494 app = wx.PyWidgetTester(size = (200, 50)) 3495 pw = cSuburbPhraseWheel(app.frame, -1) 3496 app.frame.Show(True) 3497 app.MainLoop()
3498 #--------------------------------------------------------
3499 - def test_address_type_prw():
3500 app = wx.PyWidgetTester(size = (200, 50)) 3501 pw = cAddressTypePhraseWheel(app.frame, -1) 3502 app.frame.Show(True) 3503 app.MainLoop()
3504 #--------------------------------------------------------
3505 - def test_address_prw():
3506 app = wx.PyWidgetTester(size = (200, 50)) 3507 pw = cAddressPhraseWheel(app.frame, -1) 3508 app.frame.Show(True) 3509 app.MainLoop()
3510 #--------------------------------------------------------
3511 - def test_street_prw():
3512 app = wx.PyWidgetTester(size = (200, 50)) 3513 pw = cStreetPhraseWheel(app.frame, -1) 3514 # pw.set_context(context = u'zip', val = u'04318') 3515 app.frame.Show(True) 3516 app.MainLoop()
3517 #--------------------------------------------------------
3518 - def test_organizer_pnl():
3519 app = wx.PyWidgetTester(size = (600, 400)) 3520 app.SetWidget(cKOrganizerSchedulePnl) 3521 app.MainLoop()
3522 #--------------------------------------------------------
3523 - def test_person_names_pnl():
3524 app = wx.PyWidgetTester(size = (600, 400)) 3525 widget = cPersonNamesManagerPnl(app.frame, -1) 3526 widget.identity = activate_patient() 3527 app.frame.Show(True) 3528 app.MainLoop()
3529 #--------------------------------------------------------
3530 - def test_person_ids_pnl():
3531 app = wx.PyWidgetTester(size = (600, 400)) 3532 widget = cPersonIDsManagerPnl(app.frame, -1) 3533 widget.identity = activate_patient() 3534 app.frame.Show(True) 3535 app.MainLoop()
3536 #--------------------------------------------------------
3537 - def test_pat_ids_pnl():
3538 app = wx.PyWidgetTester(size = (600, 400)) 3539 widget = cPersonIdentityManagerPnl(app.frame, -1) 3540 widget.identity = activate_patient() 3541 app.frame.Show(True) 3542 app.MainLoop()
3543 #--------------------------------------------------------
3544 - def test_name_ea_pnl():
3545 app = wx.PyWidgetTester(size = (600, 400)) 3546 app.SetWidget(cNameGenderDOBEditAreaPnl, name = activate_patient().get_active_name()) 3547 app.MainLoop()
3548 #--------------------------------------------------------
3549 - def test_address_ea_pnl():
3550 app = wx.PyWidgetTester(size = (600, 400)) 3551 app.SetWidget(cAddressEditAreaPnl, address = gmDemographicRecord.cAddress(aPK_obj = 1)) 3552 app.MainLoop()
3553 #--------------------------------------------------------
3554 - def test_person_adrs_pnl():
3555 app = wx.PyWidgetTester(size = (600, 400)) 3556 widget = cPersonAddressesManagerPnl(app.frame, -1) 3557 widget.identity = activate_patient() 3558 app.frame.Show(True) 3559 app.MainLoop()
3560 #--------------------------------------------------------
3561 - def test_person_comms_pnl():
3562 app = wx.PyWidgetTester(size = (600, 400)) 3563 widget = cPersonCommsManagerPnl(app.frame, -1) 3564 widget.identity = activate_patient() 3565 app.frame.Show(True) 3566 app.MainLoop()
3567 #--------------------------------------------------------
3568 - def test_pat_contacts_pnl():
3569 app = wx.PyWidgetTester(size = (600, 400)) 3570 widget = cPersonContactsManagerPnl(app.frame, -1) 3571 widget.identity = activate_patient() 3572 app.frame.Show(True) 3573 app.MainLoop()
3574 #--------------------------------------------------------
3575 - def test_cPersonDemographicsEditorNb():
3576 app = wx.PyWidgetTester(size = (600, 400)) 3577 widget = cPersonDemographicsEditorNb(app.frame, -1) 3578 widget.identity = activate_patient() 3579 widget.refresh() 3580 app.frame.Show(True) 3581 app.MainLoop()
3582 #--------------------------------------------------------
3583 - def activate_patient():
3584 patient = gmPerson.ask_for_patient() 3585 if patient is None: 3586 print "No patient. Exiting gracefully..." 3587 sys.exit(0) 3588 from Gnumed.wxpython import gmPatSearchWidgets 3589 gmPatSearchWidgets.set_active_patient(patient=patient) 3590 return patient
3591 #-------------------------------------------------------- 3592 if len(sys.argv) > 1 and sys.argv[1] == 'test': 3593 3594 gmI18N.activate_locale() 3595 gmI18N.install_domain(domain='gnumed') 3596 gmPG2.get_connection() 3597 3598 # a = cFormDTD(fields = cBasicPatDetailsPage.form_fields) 3599 3600 # app = wx.PyWidgetTester(size = (400, 300)) 3601 # app.SetWidget(cNotebookedPatEditionPanel, -1) 3602 # app.SetWidget(TestWizardPanel, -1) 3603 # app.frame.Show(True) 3604 # app.MainLoop() 3605 3606 # phrasewheels 3607 # test_zipcode_prw() 3608 # test_state_prw() 3609 # test_street_prw() 3610 # test_organizer_pnl() 3611 #test_address_type_prw() 3612 #test_suburb_prw() 3613 test_urb_prw() 3614 #test_address_prw() 3615 3616 # contacts related widgets 3617 #test_address_ea_pnl() 3618 #test_person_adrs_pnl() 3619 #test_person_comms_pnl() 3620 #test_pat_contacts_pnl() 3621 3622 # identity related widgets 3623 #test_person_names_pnl() 3624 #test_person_ids_pnl() 3625 #test_pat_ids_pnl() 3626 #test_name_ea_pnl() 3627 3628 #test_cPersonDemographicsEditorNb() 3629 3630 #============================================================ 3631