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['country'] 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 wxgNameGenderDOBEditAreaPnl 1611
1612 -class cNameGenderDOBEditAreaPnl(wxgNameGenderDOBEditAreaPnl.wxgNameGenderDOBEditAreaPnl):
1613 """An edit area for editing/creating name/gender/dob. 1614 1615 Does NOT act on/listen to the current patient. 1616 """
1617 - def __init__(self, *args, **kwargs):
1618 1619 self.__name = kwargs['name'] 1620 del kwargs['name'] 1621 self.__identity = gmPerson.cIdentity(aPK_obj = self.__name['pk_identity']) 1622 1623 wxgNameGenderDOBEditAreaPnl.wxgNameGenderDOBEditAreaPnl.__init__(self, *args, **kwargs) 1624 1625 self.__register_interests() 1626 self.refresh()
1627 #-------------------------------------------------------- 1628 # external API 1629 #--------------------------------------------------------
1630 - def refresh(self):
1631 if self.__name is None: 1632 return 1633 1634 self._PRW_title.SetText(gmTools.coalesce(self.__name['title'], u'')) 1635 self._PRW_firstname.SetText(self.__name['firstnames']) 1636 self._PRW_lastname.SetText(self.__name['lastnames']) 1637 self._PRW_nick.SetText(gmTools.coalesce(self.__name['preferred'], u'')) 1638 dob = self.__identity['dob'] 1639 self._PRW_dob.SetText(value = dob.strftime('%Y-%m-%d %H:%M'), data = dob) 1640 self._PRW_gender.SetData(self.__name['gender']) 1641 self._CHBOX_active.SetValue(self.__name['active_name']) 1642 self._DP_dod.SetValue(self.__identity['deceased']) 1643 self._TCTRL_comment.SetValue(gmTools.coalesce(self.__name['comment'], u''))
1644 # FIXME: clear fields 1645 # else: 1646 # pass 1647 #--------------------------------------------------------
1648 - def save(self):
1649 1650 if not self.__valid_for_save(): 1651 return False 1652 1653 self.__identity['gender'] = self._PRW_gender.GetData() 1654 if self._PRW_dob.GetValue().strip() == u'': 1655 self.__identity['dob'] = None 1656 else: 1657 self.__identity['dob'] = self._PRW_dob.GetData().get_pydt() 1658 self.__identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'') 1659 self.__identity['deceased'] = self._DP_dod.GetValue(as_pydt = True) 1660 self.__identity.save_payload() 1661 1662 active = self._CHBOX_active.GetValue() 1663 first = self._PRW_firstname.GetValue().strip() 1664 last = self._PRW_lastname.GetValue().strip() 1665 old_nick = self.__name['preferred'] 1666 1667 # is it a new name ? 1668 old_name = self.__name['firstnames'] + self.__name['lastnames'] 1669 if (first + last) != old_name: 1670 self.__name = self.__identity.add_name(first, last, active) 1671 1672 self.__name['active_name'] = active 1673 self.__name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'') 1674 self.__name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 1675 1676 self.__name.save_payload() 1677 1678 return True
1679 #-------------------------------------------------------- 1680 # event handling 1681 #--------------------------------------------------------
1682 - def __register_interests(self):
1683 self._PRW_firstname.add_callback_on_lose_focus(self._on_name_set)
1684 #--------------------------------------------------------
1685 - def _on_name_set(self):
1686 """Set the gender according to entered firstname. 1687 1688 Matches are fetched from existing records in backend. 1689 """ 1690 firstname = self._PRW_firstname.GetValue().strip() 1691 if firstname == u'': 1692 return True 1693 rows, idx = gmPG2.run_ro_queries(queries = [{ 1694 'cmd': u"select gender from dem.name_gender_map where name ilike %s", 1695 'args': [firstname] 1696 }]) 1697 if len(rows) == 0: 1698 return True 1699 wx.CallAfter(self._PRW_gender.SetData, rows[0][0]) 1700 return True
1701 #-------------------------------------------------------- 1702 # internal helpers 1703 #--------------------------------------------------------
1704 - def __valid_for_save(self):
1705 1706 has_error = False 1707 1708 if self._PRW_gender.GetData() is None: 1709 self._PRW_gender.SetBackgroundColour('pink') 1710 self._PRW_gender.Refresh() 1711 self._PRW_gender.SetFocus() 1712 has_error = True 1713 else: 1714 self._PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1715 self._PRW_gender.Refresh() 1716 1717 if not self._PRW_dob.is_valid_timestamp(): 1718 val = self._PRW_dob.GetValue().strip() 1719 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val) 1720 self._PRW_dob.SetBackgroundColour('pink') 1721 self._PRW_dob.Refresh() 1722 self._PRW_dob.SetFocus() 1723 has_error = True 1724 else: 1725 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1726 self._PRW_dob.Refresh() 1727 1728 if not self._DP_dod.is_valid_timestamp(): 1729 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.')) 1730 self._DP_dod.SetBackgroundColour('pink') 1731 self._DP_dod.Refresh() 1732 self._DP_dod.SetFocus() 1733 has_error = True 1734 else: 1735 self._DP_dod.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1736 self._DP_dod.Refresh() 1737 1738 if self._PRW_lastname.GetValue().strip() == u'': 1739 self._PRW_lastname.SetBackgroundColour('pink') 1740 self._PRW_lastname.Refresh() 1741 self._PRW_lastname.SetFocus() 1742 has_error = True 1743 else: 1744 self._PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1745 self._PRW_lastname.Refresh() 1746 1747 if self._PRW_firstname.GetValue().strip() == u'': 1748 self._PRW_firstname.SetBackgroundColour('pink') 1749 self._PRW_firstname.Refresh() 1750 self._PRW_firstname.SetFocus() 1751 has_error = True 1752 else: 1753 self._PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1754 self._PRW_firstname.Refresh() 1755 1756 return (has_error is False)
1757 #------------------------------------------------------------ 1758 # list manager 1759 #------------------------------------------------------------
1760 -class cPersonNamesManagerPnl(gmListWidgets.cGenericListManagerPnl):
1761 """A list for managing a person's names. 1762 1763 Does NOT act on/listen to the current patient. 1764 """
1765 - def __init__(self, *args, **kwargs):
1766 1767 try: 1768 self.__identity = kwargs['identity'] 1769 del kwargs['identity'] 1770 except KeyError: 1771 self.__identity = None 1772 1773 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1774 1775 self.new_callback = self._add_name 1776 self.edit_callback = self._edit_name 1777 self.delete_callback = self._del_name 1778 self.refresh_callback = self.refresh 1779 1780 self.__init_ui() 1781 self.refresh()
1782 #-------------------------------------------------------- 1783 # external API 1784 #--------------------------------------------------------
1785 - def refresh(self, *args, **kwargs):
1786 if self.__identity is None: 1787 self._LCTRL_items.set_string_items() 1788 return 1789 1790 names = self.__identity.get_names() 1791 self._LCTRL_items.set_string_items ( 1792 items = [ [ 1793 gmTools.bool2str(n['active_name'], 'X', ''), 1794 gmTools.coalesce(n['title'], gmPerson.map_gender2salutation(n['gender'])), 1795 n['lastnames'], 1796 n['firstnames'], 1797 gmTools.coalesce(n['preferred'], u''), 1798 gmTools.coalesce(n['comment'], u'') 1799 ] for n in names ] 1800 ) 1801 self._LCTRL_items.set_column_widths() 1802 self._LCTRL_items.set_data(data = names)
1803 #-------------------------------------------------------- 1804 # internal helpers 1805 #--------------------------------------------------------
1806 - def __init_ui(self):
1807 self._LCTRL_items.set_columns(columns = [ 1808 _('Active'), 1809 _('Title'), 1810 _('Lastname'), 1811 _('Firstname(s)'), 1812 _('Preferred Name'), 1813 _('Comment') 1814 ])
1815 #--------------------------------------------------------
1816 - def _add_name(self):
1817 ea = cNameGenderDOBEditAreaPnl(self, -1, name = self.__identity.get_active_name()) 1818 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1819 dlg.SetTitle(_('Adding new name')) 1820 if dlg.ShowModal() == wx.ID_OK: 1821 dlg.Destroy() 1822 return True 1823 dlg.Destroy() 1824 return False
1825 #--------------------------------------------------------
1826 - def _edit_name(self, name):
1827 ea = cNameGenderDOBEditAreaPnl(self, -1, name = name) 1828 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1829 dlg.SetTitle(_('Editing name')) 1830 if dlg.ShowModal() == wx.ID_OK: 1831 dlg.Destroy() 1832 return True 1833 dlg.Destroy() 1834 return False
1835 #--------------------------------------------------------
1836 - def _del_name(self, name):
1837 1838 if len(self.__identity.get_names()) == 1: 1839 gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the only name of a person.'), beep = True) 1840 return False 1841 1842 go_ahead = gmGuiHelpers.gm_show_question ( 1843 _( 'It is often advisable to keep old names around and\n' 1844 'just create a new "currently active" name.\n' 1845 '\n' 1846 'This allows finding the patient by both the old\n' 1847 'and the new name (think before/after marriage).\n' 1848 '\n' 1849 'Do you still want to really delete\n' 1850 "this name from the patient ?" 1851 ), 1852 _('Deleting name') 1853 ) 1854 if not go_ahead: 1855 return False 1856 1857 self.__identity.delete_name(name = name) 1858 return True
1859 #-------------------------------------------------------- 1860 # properties 1861 #--------------------------------------------------------
1862 - def _get_identity(self):
1863 return self.__identity
1864
1865 - def _set_identity(self, identity):
1866 self.__identity = identity 1867 self.refresh()
1868 1869 identity = property(_get_identity, _set_identity)
1870 #------------------------------------------------------------
1871 -class cPersonIDsManagerPnl(gmListWidgets.cGenericListManagerPnl):
1872 """A list for managing a person's external IDs. 1873 1874 Does NOT act on/listen to the current patient. 1875 """
1876 - def __init__(self, *args, **kwargs):
1877 1878 try: 1879 self.__identity = kwargs['identity'] 1880 del kwargs['identity'] 1881 except KeyError: 1882 self.__identity = None 1883 1884 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1885 1886 self.new_callback = self._add_id 1887 self.edit_callback = self._edit_id 1888 self.delete_callback = self._del_id 1889 self.refresh_callback = self.refresh 1890 1891 self.__init_ui() 1892 self.refresh()
1893 #-------------------------------------------------------- 1894 # external API 1895 #--------------------------------------------------------
1896 - def refresh(self, *args, **kwargs):
1897 if self.__identity is None: 1898 self._LCTRL_items.set_string_items() 1899 return 1900 1901 ids = self.__identity.get_external_ids() 1902 self._LCTRL_items.set_string_items ( 1903 items = [ [ 1904 i['name'], 1905 i['value'], 1906 gmTools.coalesce(i['issuer'], u''), 1907 i['context'], 1908 gmTools.coalesce(i['comment'], u'') 1909 ] for i in ids 1910 ] 1911 ) 1912 self._LCTRL_items.set_column_widths() 1913 self._LCTRL_items.set_data(data = ids)
1914 #-------------------------------------------------------- 1915 # internal helpers 1916 #--------------------------------------------------------
1917 - def __init_ui(self):
1918 self._LCTRL_items.set_columns(columns = [ 1919 _('ID type'), 1920 _('Value'), 1921 _('Issuer'), 1922 _('Context'), 1923 _('Comment') 1924 ])
1925 #--------------------------------------------------------
1926 - def _add_id(self):
1927 ea = cExternalIDEditAreaPnl(self, -1) 1928 ea.identity = self.__identity 1929 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1930 dlg.SetTitle(_('Adding new external ID')) 1931 if dlg.ShowModal() == wx.ID_OK: 1932 dlg.Destroy() 1933 return True 1934 dlg.Destroy() 1935 return False
1936 #--------------------------------------------------------
1937 - def _edit_id(self, ext_id):
1938 ea = cExternalIDEditAreaPnl(self, -1, external_id = ext_id) 1939 ea.identity = self.__identity 1940 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1941 dlg.SetTitle(_('Editing external ID')) 1942 if dlg.ShowModal() == wx.ID_OK: 1943 dlg.Destroy() 1944 return True 1945 dlg.Destroy() 1946 return False
1947 #--------------------------------------------------------
1948 - def _del_id(self, ext_id):
1949 go_ahead = gmGuiHelpers.gm_show_question ( 1950 _( 'Do you really want to delete this\n' 1951 'external ID from the patient ?'), 1952 _('Deleting external ID') 1953 ) 1954 if not go_ahead: 1955 return False 1956 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id']) 1957 return True
1958 #-------------------------------------------------------- 1959 # properties 1960 #--------------------------------------------------------
1961 - def _get_identity(self):
1962 return self.__identity
1963
1964 - def _set_identity(self, identity):
1965 self.__identity = identity 1966 self.refresh()
1967 1968 identity = property(_get_identity, _set_identity)
1969 #------------------------------------------------------------ 1970 # integrated panels 1971 #------------------------------------------------------------
1972 -class cPersonIdentityManagerPnl(wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl):
1973 """A panel for editing identity data for a person. 1974 1975 - provides access to: 1976 - name 1977 - external IDs 1978 1979 Does NOT act on/listen to the current patient. 1980 """
1981 - def __init__(self, *args, **kwargs):
1982 1983 wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl.__init__(self, *args, **kwargs) 1984 1985 self.__identity = None 1986 self.refresh()
1987 #-------------------------------------------------------- 1988 # external API 1989 #--------------------------------------------------------
1990 - def refresh(self):
1991 self._PNL_names.identity = self.__identity 1992 self._PNL_ids.identity = self.__identity
1993 #-------------------------------------------------------- 1994 # properties 1995 #--------------------------------------------------------
1996 - def _get_identity(self):
1997 return self.__identity
1998
1999 - def _set_identity(self, identity):
2000 self.__identity = identity 2001 self.refresh()
2002 2003 identity = property(_get_identity, _set_identity)
2004 #============================================================ 2005 # new-patient widgets 2006 #============================================================
2007 -def create_new_person(parent=None, activate=False):
2008 2009 dbcfg = gmCfg.cCfgSQL() 2010 2011 def_region = dbcfg.get2 ( 2012 option = u'person.create.default_region', 2013 workplace = gmSurgery.gmCurrentPractice().active_workplace, 2014 bias = u'user' 2015 ) 2016 2017 if def_region is None: 2018 def_country = dbcfg.get2 ( 2019 option = u'person.create.default_country', 2020 workplace = gmSurgery.gmCurrentPractice().active_workplace, 2021 bias = u'user' 2022 ) 2023 else: 2024 countries = gmDemographicRecord.get_country_for_region(region = def_region) 2025 if len(countries) == 1: 2026 def_country = countries[0]['l10n_country'] 2027 2028 if parent is None: 2029 parent = wx.GetApp().GetTopWindow() 2030 2031 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region) 2032 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) 2033 dlg.SetTitle(_('Adding new person')) 2034 ea._PRW_lastname.SetFocus() 2035 result = dlg.ShowModal() 2036 pat = ea.data 2037 dlg.Destroy() 2038 2039 if result != wx.ID_OK: 2040 return False 2041 2042 if activate: 2043 from Gnumed.wxpython import gmPatSearchWidgets 2044 gmPatSearchWidgets.set_active_patient(patient = pat) 2045 2046 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin') 2047 2048 return True
2049 #============================================================ 2050 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl 2051
2052 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
2053
2054 - def __init__(self, *args, **kwargs):
2055 2056 try: 2057 self.default_region = kwargs['region'] 2058 del kwargs['region'] 2059 except KeyError: 2060 self.default_region = None 2061 2062 try: 2063 self.default_country = kwargs['country'] 2064 del kwargs['country'] 2065 except KeyError: 2066 self.default_country = None 2067 2068 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs) 2069 gmEditArea.cGenericEditAreaMixin.__init__(self) 2070 2071 self.mode = 'new' 2072 self.data = None 2073 self._address = None 2074 2075 self.__init_ui() 2076 self.__register_interests()
2077 #---------------------------------------------------------------- 2078 # internal helpers 2079 #----------------------------------------------------------------
2080 - def __init_ui(self):
2081 self._PRW_lastname.final_regex = '.+' 2082 self._PRW_firstnames.final_regex = '.+' 2083 self._PRW_address_searcher.selection_only = False 2084 low = wx.DateTimeFromDMY(1,0,1900) 2085 hi = wx.DateTime() 2086 self._DP_dob.SetRange(low, hi.SetToCurrent()) 2087 # only if we would support None on selection_only's 2088 #self._PRW_external_id_type.selection_only = True 2089 2090 if self.default_country is not None: 2091 self._PRW_country.SetText(value = self.default_country) 2092 2093 if self.default_region is not None: 2094 self._PRW_region.SetText(value = self.default_region)
2095 #----------------------------------------------------------------
2096 - def __perhaps_invalidate_address_searcher(self, ctrl=None, field=None):
2097 2098 adr = self._PRW_address_searcher.get_address() 2099 if adr is None: 2100 return True 2101 2102 if ctrl.GetValue().strip() != adr[field]: 2103 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None) 2104 return True 2105 2106 return False
2107 #----------------------------------------------------------------
2109 adr = self._PRW_address_searcher.get_address() 2110 if adr is None: 2111 return True 2112 2113 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode']) 2114 2115 self._PRW_street.SetText(value = adr['street'], data = adr['street']) 2116 self._PRW_street.set_context(context = u'zip', val = adr['postcode']) 2117 2118 self._TCTRL_number.SetValue(adr['number']) 2119 2120 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb']) 2121 self._PRW_urb.set_context(context = u'zip', val = adr['postcode']) 2122 2123 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state']) 2124 self._PRW_region.set_context(context = u'zip', val = adr['postcode']) 2125 2126 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country']) 2127 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
2128 #----------------------------------------------------------------
2129 - def __identity_valid_for_save(self):
2130 error = False 2131 2132 # name fields 2133 if self._PRW_lastname.GetValue().strip() == u'': 2134 error = True 2135 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.')) 2136 self._PRW_lastname.display_as_valid(False) 2137 else: 2138 self._PRW_lastname.display_as_valid(True) 2139 2140 if self._PRW_firstnames.GetValue().strip() == '': 2141 error = True 2142 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.')) 2143 self._PRW_firstnames.display_as_valid(False) 2144 else: 2145 self._PRW_firstnames.display_as_valid(True) 2146 2147 # gender 2148 if self._PRW_gender.GetData() is None: 2149 error = True 2150 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.')) 2151 self._PRW_gender.display_as_valid(False) 2152 else: 2153 self._PRW_gender.display_as_valid(True) 2154 2155 # dob validation 2156 if not self._DP_dob.is_valid_timestamp(): 2157 2158 gmDispatcher.send(signal = 'statustext', msg = _('Cannot use this date of birth. Does it lie before 1900 ?')) 2159 2160 do_it_anyway = gmGuiHelpers.gm_show_question ( 2161 _( 2162 'Are you sure you want to register this person\n' 2163 'without a valid date of birth ?\n' 2164 '\n' 2165 'This can be useful for temporary staff members\n' 2166 'but will provoke nag screens if this person\n' 2167 'becomes a patient.\n' 2168 '\n' 2169 'Note that the date of birth cannot technically\n' 2170 'be before 1900, either :-(\n' 2171 ), 2172 _('Registering new person') 2173 ) 2174 2175 if not do_it_anyway: 2176 error = True 2177 2178 if self._DP_dob.GetValue() is None: 2179 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 2180 elif self._DP_dob.GetValue().GetYear() < 1900: 2181 error = True 2182 gmDispatcher.send(signal = 'statustext', msg = _('The year of birth must lie after 1900.'), beep = True) 2183 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 2184 self._DP_dob.SetFocus() 2185 else: 2186 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 2187 self._DP_dob.Refresh() 2188 2189 # TOB validation if non-empty 2190 # if self._TCTRL_tob.GetValue().strip() != u'': 2191 2192 return (not error)
2193 #----------------------------------------------------------------
2194 - def __address_valid_for_save(self, empty_address_is_valid=False):
2195 2196 # existing address ? if so set other fields 2197 if self._PRW_address_searcher.GetData() is not None: 2198 wx.CallAfter(self.__set_fields_from_address_searcher) 2199 return True 2200 2201 # must either all contain something or none of them 2202 fields_to_fill = ( 2203 self._TCTRL_number, 2204 self._PRW_zip, 2205 self._PRW_street, 2206 self._PRW_urb, 2207 self._PRW_region, 2208 self._PRW_country 2209 ) 2210 no_of_filled_fields = 0 2211 2212 for field in fields_to_fill: 2213 if field.GetValue().strip() != u'': 2214 no_of_filled_fields += 1 2215 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 2216 field.Refresh() 2217 2218 # empty address ? 2219 if no_of_filled_fields == 0: 2220 if empty_address_is_valid: 2221 return True 2222 else: 2223 return None 2224 2225 # incompletely filled address ? 2226 if no_of_filled_fields != len(fields_to_fill): 2227 for field in fields_to_fill: 2228 if field.GetValue().strip() == u'': 2229 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 2230 field.SetFocus() 2231 field.Refresh() 2232 msg = _('To properly create an address, all the related fields must be filled in.') 2233 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 2234 return False 2235 2236 # fields which must contain a selected item 2237 # FIXME: they must also contain an *acceptable combination* which 2238 # FIXME: can only be tested against the database itself ... 2239 strict_fields = ( 2240 self._PRW_region, 2241 self._PRW_country 2242 ) 2243 error = False 2244 for field in strict_fields: 2245 if field.GetData() is None: 2246 error = True 2247 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 2248 field.SetFocus() 2249 else: 2250 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 2251 field.Refresh() 2252 2253 if error: 2254 msg = _('This field must contain an item selected from the dropdown list.') 2255 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 2256 return False 2257 2258 return True
2259 #----------------------------------------------------------------
2260 - def __register_interests(self):
2261 2262 # identity 2263 self._PRW_firstnames.add_callback_on_lose_focus(self._on_leaving_firstname) 2264 2265 # address 2266 self._PRW_address_searcher.add_callback_on_lose_focus(self._on_leaving_adress_searcher) 2267 2268 # invalidate address searcher when any field edited 2269 self._PRW_street.add_callback_on_lose_focus(self._invalidate_address_searcher) 2270 wx.EVT_KILL_FOCUS(self._TCTRL_number, self._invalidate_address_searcher) 2271 self._PRW_urb.add_callback_on_lose_focus(self._invalidate_address_searcher) 2272 self._PRW_region.add_callback_on_lose_focus(self._invalidate_address_searcher) 2273 2274 self._PRW_zip.add_callback_on_lose_focus(self._on_leaving_zip) 2275 self._PRW_country.add_callback_on_lose_focus(self._on_leaving_country)
2276 #---------------------------------------------------------------- 2277 # event handlers 2278 #----------------------------------------------------------------
2279 - def _on_leaving_firstname(self):
2280 """Set the gender according to entered firstname. 2281 2282 Matches are fetched from existing records in backend. 2283 """ 2284 # only set if not already set so as to not 2285 # overwrite a change by the user 2286 if self._PRW_gender.GetData() is not None: 2287 return True 2288 2289 firstname = self._PRW_firstnames.GetValue().strip() 2290 if firstname == u'': 2291 return True 2292 2293 gender = gmPerson.map_firstnames2gender(firstnames = firstname) 2294 if gender is None: 2295 return True 2296 2297 wx.CallAfter(self._PRW_gender.SetData, gender) 2298 return True
2299 #----------------------------------------------------------------
2300 - def _on_leaving_zip(self):
2301 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode') 2302 2303 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'') 2304 self._PRW_street.set_context(context = u'zip', val = zip_code) 2305 self._PRW_urb.set_context(context = u'zip', val = zip_code) 2306 self._PRW_region.set_context(context = u'zip', val = zip_code) 2307 self._PRW_country.set_context(context = u'zip', val = zip_code) 2308 2309 return True
2310 #----------------------------------------------------------------
2311 - def _on_leaving_country(self):
2312 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country') 2313 2314 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'') 2315 self._PRW_region.set_context(context = u'country', val = country) 2316 2317 return True
2318 #----------------------------------------------------------------
2319 - def _invalidate_address_searcher(self, *args, **kwargs):
2320 mapping = [ 2321 (self._PRW_street, 'street'), 2322 (self._TCTRL_number, 'number'), 2323 (self._PRW_urb, 'urb'), 2324 (self._PRW_region, 'l10n_state') 2325 ] 2326 2327 # loop through fields and invalidate address searcher if different 2328 for ctrl, field in mapping: 2329 if self.__perhaps_invalidate_address_searcher(ctrl, field): 2330 return True 2331 2332 return True
2333 #----------------------------------------------------------------
2335 adr = self._PRW_address_searcher.get_address() 2336 if adr is None: 2337 return True 2338 2339 wx.CallAfter(self.__set_fields_from_address_searcher) 2340 return True
2341 #---------------------------------------------------------------- 2342 # generic Edit Area mixin API 2343 #----------------------------------------------------------------
2344 - def _valid_for_save(self):
2345 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
2346 #----------------------------------------------------------------
2347 - def _save_as_new(self):
2348 2349 # identity 2350 new_identity = gmPerson.create_identity ( 2351 gender = self._PRW_gender.GetData(), 2352 dob = self._DP_dob.get_pydt(), 2353 lastnames = self._PRW_lastname.GetValue().strip(), 2354 firstnames = self._PRW_firstnames.GetValue().strip() 2355 ) 2356 _log.debug('identity created: %s' % new_identity) 2357 2358 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip()) 2359 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u'')) 2360 #TOB 2361 new_identity.save() 2362 2363 name = new_identity.get_active_name() 2364 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 2365 name.save() 2366 2367 # address 2368 is_valid = self.__address_valid_for_save(empty_address_is_valid = False) 2369 if is_valid is True: 2370 # because we currently only check for non-emptiness 2371 # we must still deal with database errors 2372 try: 2373 new_identity.link_address ( 2374 number = self._TCTRL_number.GetValue().strip(), 2375 street = self._PRW_street.GetValue().strip(), 2376 postcode = self._PRW_zip.GetValue().strip(), 2377 urb = self._PRW_urb.GetValue().strip(), 2378 state = self._PRW_region.GetData(), 2379 country = self._PRW_country.GetData() 2380 ) 2381 except psycopg2.InternalError: 2382 #except StandardError: 2383 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip()) 2384 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip()) 2385 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip()) 2386 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip()) 2387 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip()) 2388 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip()) 2389 _log.exception('cannot link address') 2390 gmGuiHelpers.gm_show_error ( 2391 aTitle = _('Saving address'), 2392 aMessage = _( 2393 'Cannot save this address.\n' 2394 '\n' 2395 'You will have to add it via the Demographics plugin.\n' 2396 ) 2397 ) 2398 elif is_valid is False: 2399 gmGuiHelpers.gm_show_error ( 2400 aTitle = _('Saving address'), 2401 aMessage = _( 2402 'Address not saved.\n' 2403 '\n' 2404 'You will have to add it via the Demographics plugin.\n' 2405 ) 2406 ) 2407 # else it is None which means empty address which we ignore 2408 2409 # phone 2410 new_identity.link_comm_channel ( 2411 comm_medium = u'homephone', 2412 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''), 2413 is_confidential = False 2414 ) 2415 2416 # external ID 2417 pk_type = self._PRW_external_id_type.GetData() 2418 id_value = self._TCTRL_external_id_value.GetValue().strip() 2419 if (pk_type is not None) and (id_value != u''): 2420 new_identity.add_external_id(value = id_value, pk_type = pk_type) 2421 2422 # occupation 2423 new_identity.link_occupation ( 2424 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'') 2425 ) 2426 2427 self.data = new_identity 2428 return True
2429 #----------------------------------------------------------------
2430 - def _save_as_update(self):
2431 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2432 #----------------------------------------------------------------
2433 - def _refresh_as_new(self):
2434 # FIXME: button "empty out" 2435 return
2436 #----------------------------------------------------------------
2437 - def _refresh_from_existing(self):
2438 return # there is no forward button so nothing to do here
2439 #----------------------------------------------------------------
2441 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2442 #============================================================ 2443 # new-patient wizard classes 2444 #============================================================
2445 -class cBasicPatDetailsPage(wx.wizard.WizardPageSimple):
2446 """ 2447 Wizard page for entering patient's basic demographic information 2448 """ 2449 2450 form_fields = ( 2451 'firstnames', 'lastnames', 'nick', 'dob', 'gender', 'title', 'occupation', 2452 'address_number', 'zip_code', 'street', 'town', 'state', 'country', 'phone', 'comment' 2453 ) 2454
2455 - def __init__(self, parent, title):
2456 """ 2457 Creates a new instance of BasicPatDetailsPage 2458 @param parent - The parent widget 2459 @type parent - A wx.Window instance 2460 @param tile - The title of the page 2461 @type title - A StringType instance 2462 """ 2463 wx.wizard.WizardPageSimple.__init__(self, parent) #, bitmap = gmGuiHelpers.gm_icon(_('oneperson')) 2464 self.__title = title 2465 self.__do_layout() 2466 self.__register_interests()
2467 #--------------------------------------------------------
2468 - def __do_layout(self):
2469 PNL_form = wx.Panel(self, -1) 2470 2471 # last name 2472 STT_lastname = wx.StaticText(PNL_form, -1, _('Last name')) 2473 STT_lastname.SetForegroundColour('red') 2474 self.PRW_lastname = cLastnamePhraseWheel(parent = PNL_form, id = -1) 2475 self.PRW_lastname.SetToolTipString(_('Required: lastname (family name)')) 2476 2477 # first name 2478 STT_firstname = wx.StaticText(PNL_form, -1, _('First name(s)')) 2479 STT_firstname.SetForegroundColour('red') 2480 self.PRW_firstname = cFirstnamePhraseWheel(parent = PNL_form, id = -1) 2481 self.PRW_firstname.SetToolTipString(_('Required: surname/given name/first name')) 2482 2483 # nickname 2484 STT_nick = wx.StaticText(PNL_form, -1, _('Nick name')) 2485 self.PRW_nick = cNicknamePhraseWheel(parent = PNL_form, id = -1) 2486 2487 # DOB 2488 STT_dob = wx.StaticText(PNL_form, -1, _('Date of birth')) 2489 STT_dob.SetForegroundColour('red') 2490 self.PRW_dob = gmDateTimeInput.cFuzzyTimestampInput(parent = PNL_form, id = -1) 2491 self.PRW_dob.SetToolTipString(_("Required: date of birth, if unknown or aliasing wanted then invent one")) 2492 2493 # gender 2494 STT_gender = wx.StaticText(PNL_form, -1, _('Gender')) 2495 STT_gender.SetForegroundColour('red') 2496 self.PRW_gender = cGenderSelectionPhraseWheel(parent = PNL_form, id=-1) 2497 self.PRW_gender.SetToolTipString(_("Required: gender of patient")) 2498 2499 # title 2500 STT_title = wx.StaticText(PNL_form, -1, _('Title')) 2501 self.PRW_title = cTitlePhraseWheel(parent = PNL_form, id = -1) 2502 2503 # zip code 2504 STT_zip_code = wx.StaticText(PNL_form, -1, _('Postal code')) 2505 STT_zip_code.SetForegroundColour('orange') 2506 self.PRW_zip_code = cZipcodePhraseWheel(parent = PNL_form, id = -1) 2507 self.PRW_zip_code.SetToolTipString(_("primary/home address: zip/postal code")) 2508 2509 # street 2510 STT_street = wx.StaticText(PNL_form, -1, _('Street')) 2511 STT_street.SetForegroundColour('orange') 2512 self.PRW_street = cStreetPhraseWheel(parent = PNL_form, id = -1) 2513 self.PRW_street.SetToolTipString(_("primary/home address: name of street")) 2514 2515 # address number 2516 STT_address_number = wx.StaticText(PNL_form, -1, _('Number')) 2517 STT_address_number.SetForegroundColour('orange') 2518 self.TTC_address_number = wx.TextCtrl(PNL_form, -1) 2519 self.TTC_address_number.SetToolTipString(_("primary/home address: address number")) 2520 2521 # town 2522 STT_town = wx.StaticText(PNL_form, -1, _('Place')) 2523 STT_town.SetForegroundColour('orange') 2524 self.PRW_town = cUrbPhraseWheel(parent = PNL_form, id = -1) 2525 self.PRW_town.SetToolTipString(_("primary/home address: city/town/village/dwelling/...")) 2526 2527 # state 2528 STT_state = wx.StaticText(PNL_form, -1, _('Region')) 2529 STT_state.SetForegroundColour('orange') 2530 self.PRW_state = cStateSelectionPhraseWheel(parent=PNL_form, id=-1) 2531 self.PRW_state.SetToolTipString(_("primary/home address: state/province/county/...")) 2532 2533 # country 2534 STT_country = wx.StaticText(PNL_form, -1, _('Country')) 2535 STT_country.SetForegroundColour('orange') 2536 self.PRW_country = cCountryPhraseWheel(parent = PNL_form, id = -1) 2537 self.PRW_country.SetToolTipString(_("primary/home address: country")) 2538 2539 # phone 2540 STT_phone = wx.StaticText(PNL_form, -1, _('Phone')) 2541 self.TTC_phone = wx.TextCtrl(PNL_form, -1) 2542 self.TTC_phone.SetToolTipString(_("phone number at home")) 2543 2544 # occupation 2545 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation')) 2546 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1) 2547 2548 # comment 2549 STT_comment = wx.StaticText(PNL_form, -1, _('Comment')) 2550 self.TCTRL_comment = wx.TextCtrl(PNL_form, -1) 2551 self.TCTRL_comment.SetToolTipString(_('A comment on this patient.')) 2552 2553 # form main validator 2554 self.form_DTD = cFormDTD(fields = self.__class__.form_fields) 2555 PNL_form.SetValidator(cBasicPatDetailsPageValidator(dtd = self.form_DTD)) 2556 2557 # layout input widgets 2558 SZR_input = wx.FlexGridSizer(cols = 2, rows = 16, vgap = 4, hgap = 4) 2559 SZR_input.AddGrowableCol(1) 2560 SZR_input.Add(STT_lastname, 0, wx.SHAPED) 2561 SZR_input.Add(self.PRW_lastname, 1, wx.EXPAND) 2562 SZR_input.Add(STT_firstname, 0, wx.SHAPED) 2563 SZR_input.Add(self.PRW_firstname, 1, wx.EXPAND) 2564 SZR_input.Add(STT_nick, 0, wx.SHAPED) 2565 SZR_input.Add(self.PRW_nick, 1, wx.EXPAND) 2566 SZR_input.Add(STT_dob, 0, wx.SHAPED) 2567 SZR_input.Add(self.PRW_dob, 1, wx.EXPAND) 2568 SZR_input.Add(STT_gender, 0, wx.SHAPED) 2569 SZR_input.Add(self.PRW_gender, 1, wx.EXPAND) 2570 SZR_input.Add(STT_title, 0, wx.SHAPED) 2571 SZR_input.Add(self.PRW_title, 1, wx.EXPAND) 2572 SZR_input.Add(STT_zip_code, 0, wx.SHAPED) 2573 SZR_input.Add(self.PRW_zip_code, 1, wx.EXPAND) 2574 SZR_input.Add(STT_street, 0, wx.SHAPED) 2575 SZR_input.Add(self.PRW_street, 1, wx.EXPAND) 2576 SZR_input.Add(STT_address_number, 0, wx.SHAPED) 2577 SZR_input.Add(self.TTC_address_number, 1, wx.EXPAND) 2578 SZR_input.Add(STT_town, 0, wx.SHAPED) 2579 SZR_input.Add(self.PRW_town, 1, wx.EXPAND) 2580 SZR_input.Add(STT_state, 0, wx.SHAPED) 2581 SZR_input.Add(self.PRW_state, 1, wx.EXPAND) 2582 SZR_input.Add(STT_country, 0, wx.SHAPED) 2583 SZR_input.Add(self.PRW_country, 1, wx.EXPAND) 2584 SZR_input.Add(STT_phone, 0, wx.SHAPED) 2585 SZR_input.Add(self.TTC_phone, 1, wx.EXPAND) 2586 SZR_input.Add(STT_occupation, 0, wx.SHAPED) 2587 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND) 2588 SZR_input.Add(STT_comment, 0, wx.SHAPED) 2589 SZR_input.Add(self.TCTRL_comment, 1, wx.EXPAND) 2590 2591 PNL_form.SetSizerAndFit(SZR_input) 2592 2593 # layout page 2594 SZR_main = gmGuiHelpers.makePageTitle(self, self.__title) 2595 SZR_main.Add(PNL_form, 1, wx.EXPAND)
2596 #-------------------------------------------------------- 2597 # event handling 2598 #--------------------------------------------------------
2599 - def __register_interests(self):
2600 self.PRW_firstname.add_callback_on_lose_focus(self.on_name_set) 2601 self.PRW_country.add_callback_on_selection(self.on_country_selected) 2602 self.PRW_zip_code.add_callback_on_lose_focus(self.on_zip_set)
2603 #--------------------------------------------------------
2604 - def on_country_selected(self, data):
2605 """Set the states according to entered country.""" 2606 self.PRW_state.set_context(context=u'country', val=data) 2607 return True
2608 #--------------------------------------------------------
2609 - def on_name_set(self):
2610 """Set the gender according to entered firstname. 2611 2612 Matches are fetched from existing records in backend. 2613 """ 2614 firstname = self.PRW_firstname.GetValue().strip() 2615 rows, idx = gmPG2.run_ro_queries(queries = [{ 2616 'cmd': u"select gender from dem.name_gender_map where name ilike %s", 2617 'args': [firstname] 2618 }]) 2619 if len(rows) == 0: 2620 return True 2621 wx.CallAfter(self.PRW_gender.SetData, rows[0][0]) 2622 return True
2623 #--------------------------------------------------------
2624 - def on_zip_set(self):
2625 """Set the street, town, state and country according to entered zip code.""" 2626 zip_code = self.PRW_zip_code.GetValue().strip() 2627 self.PRW_street.set_context(context=u'zip', val=zip_code) 2628 self.PRW_town.set_context(context=u'zip', val=zip_code) 2629 self.PRW_state.set_context(context=u'zip', val=zip_code) 2630 self.PRW_country.set_context(context=u'zip', val=zip_code) 2631 return True
2632 #============================================================
2633 -class cNewPatientWizard(wx.wizard.Wizard):
2634 """ 2635 Wizard to create a new patient. 2636 2637 TODO: 2638 - write pages for different "themes" of patient creation 2639 - make it configurable which pages are loaded 2640 - make available sets of pages that apply to a country 2641 - make loading of some pages depend upon values in earlier pages, eg 2642 when the patient is female and older than 13 include a page about 2643 "female" data (number of kids etc) 2644 2645 FIXME: use: wizard.FindWindowById(wx.ID_FORWARD).Disable() 2646 """ 2647 #--------------------------------------------------------
2648 - def __init__(self, parent, title = _('Register new person'), subtitle = _('Basic demographic details') ):
2649 """ 2650 Creates a new instance of NewPatientWizard 2651 @param parent - The parent widget 2652 @type parent - A wx.Window instance 2653 """ 2654 id_wiz = wx.NewId() 2655 wx.wizard.Wizard.__init__(self, parent, id_wiz, title) #images.getWizTest1Bitmap() 2656 self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY) 2657 self.__subtitle = subtitle 2658 self.__do_layout()
2659 #--------------------------------------------------------
2660 - def RunWizard(self, activate=False):
2661 """Create new patient. 2662 2663 activate, too, if told to do so (and patient successfully created) 2664 """ 2665 while True: 2666 2667 if not wx.wizard.Wizard.RunWizard(self, self.basic_pat_details): 2668 return False 2669 2670 try: 2671 # retrieve DTD and create patient 2672 ident = create_identity_from_dtd(dtd = self.basic_pat_details.form_DTD) 2673 except: 2674 _log.exception('cannot add new patient - missing identity fields') 2675 gmGuiHelpers.gm_show_error ( 2676 _('Cannot create new patient.\n' 2677 'Missing parts of the identity.' 2678 ), 2679 _('Adding new patient') 2680 ) 2681 continue 2682 2683 update_identity_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 2684 2685 try: 2686 link_contacts_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 2687 except: 2688 _log.exception('cannot finalize new patient - missing address fields') 2689 gmGuiHelpers.gm_show_error ( 2690 _('Cannot add address for the new patient.\n' 2691 'You must either enter all of the address fields or\n' 2692 'none at all. The relevant fields are marked in yellow.\n' 2693 '\n' 2694 'You will need to add the address details in the\n' 2695 'demographics module.' 2696 ), 2697 _('Adding new patient') 2698 ) 2699 break 2700 2701 link_occupation_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 2702 2703 break 2704 2705 if activate: 2706 from Gnumed.wxpython import gmPatSearchWidgets 2707 gmPatSearchWidgets.set_active_patient(patient = ident) 2708 2709 return ident
2710 #-------------------------------------------------------- 2711 # internal helpers 2712 #--------------------------------------------------------
2713 - def __do_layout(self):
2714 """Arrange widgets. 2715 """ 2716 # Create the wizard pages 2717 self.basic_pat_details = cBasicPatDetailsPage(self, self.__subtitle ) 2718 self.FitToPage(self.basic_pat_details)
2719 #============================================================
2720 -class cBasicPatDetailsPageValidator(wx.PyValidator):
2721 """ 2722 This validator is used to ensure that the user has entered all 2723 the required conditional values in the page (eg., to properly 2724 create an address, all the related fields must be filled). 2725 """ 2726 #--------------------------------------------------------
2727 - def __init__(self, dtd):
2728 """ 2729 Validator initialization. 2730 @param dtd The object containing the data model. 2731 @type dtd A cFormDTD instance 2732 """ 2733 # initialize parent class 2734 wx.PyValidator.__init__(self) 2735 # validator's storage object 2736 self.form_DTD = dtd
2737 #--------------------------------------------------------
2738 - def Clone(self):
2739 """ 2740 Standard cloner. 2741 Note that every validator must implement the Clone() method. 2742 """ 2743 return cBasicPatDetailsPageValidator(dtd = self.form_DTD) # FIXME: probably need new instance of DTD ?
2744 #--------------------------------------------------------
2745 - def Validate(self, parent = None):
2746 """ 2747 Validate the contents of the given text control. 2748 """ 2749 _pnl_form = self.GetWindow().GetParent() 2750 2751 error = False 2752 2753 # name fields 2754 if _pnl_form.PRW_lastname.GetValue().strip() == '': 2755 error = True 2756 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.')) 2757 _pnl_form.PRW_lastname.SetBackgroundColour('pink') 2758 _pnl_form.PRW_lastname.Refresh() 2759 else: 2760 _pnl_form.PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2761 _pnl_form.PRW_lastname.Refresh() 2762 2763 if _pnl_form.PRW_firstname.GetValue().strip() == '': 2764 error = True 2765 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.')) 2766 _pnl_form.PRW_firstname.SetBackgroundColour('pink') 2767 _pnl_form.PRW_firstname.Refresh() 2768 else: 2769 _pnl_form.PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2770 _pnl_form.PRW_firstname.Refresh() 2771 2772 # gender 2773 if _pnl_form.PRW_gender.GetData() is None: 2774 error = True 2775 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.')) 2776 _pnl_form.PRW_gender.SetBackgroundColour('pink') 2777 _pnl_form.PRW_gender.Refresh() 2778 else: 2779 _pnl_form.PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2780 _pnl_form.PRW_gender.Refresh() 2781 2782 # dob validation 2783 if ( 2784 (_pnl_form.PRW_dob.GetValue().strip() == u'') 2785 or (not _pnl_form.PRW_dob.is_valid_timestamp()) 2786 or (_pnl_form.PRW_dob.GetData().timestamp.year < 1900) 2787 ): 2788 error = True 2789 msg = _('Cannot parse <%s> into proper timestamp.') % _pnl_form.PRW_dob.GetValue() 2790 gmDispatcher.send(signal = 'statustext', msg = msg) 2791 _pnl_form.PRW_dob.SetBackgroundColour('pink') 2792 _pnl_form.PRW_dob.Refresh() 2793 else: 2794 _pnl_form.PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2795 _pnl_form.PRW_dob.Refresh() 2796 2797 # address 2798 is_any_field_filled = False 2799 address_fields = ( 2800 _pnl_form.TTC_address_number, 2801 _pnl_form.PRW_zip_code, 2802 _pnl_form.PRW_street, 2803 _pnl_form.PRW_town 2804 ) 2805 for field in address_fields: 2806 if field.GetValue().strip() == u'': 2807 if is_any_field_filled: 2808 error = True 2809 msg = _('To properly create an address, all the related fields must be filled in.') 2810 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 2811 field.SetBackgroundColour('pink') 2812 field.SetFocus() 2813 field.Refresh() 2814 else: 2815 is_any_field_filled = True 2816 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2817 field.Refresh() 2818 2819 address_fields = ( 2820 _pnl_form.PRW_state, 2821 _pnl_form.PRW_country 2822 ) 2823 for field in address_fields: 2824 if field.GetData() is None: 2825 if is_any_field_filled: 2826 error = True 2827 msg = _('To properly create an address, all the related fields must be filled in.') 2828 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 2829 field.SetBackgroundColour('pink') 2830 field.SetFocus() 2831 field.Refresh() 2832 else: 2833 is_any_field_filled = True 2834 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2835 field.Refresh() 2836 2837 return (not error)
2838 #--------------------------------------------------------
2839 - def TransferToWindow(self):
2840 """ 2841 Transfer data from validator to window. 2842 The default implementation returns False, indicating that an error 2843 occurred. We simply return True, as we don't do any data transfer. 2844 """ 2845 _pnl_form = self.GetWindow().GetParent() 2846 # fill in controls with values from self.form_DTD 2847 _pnl_form.PRW_gender.SetData(self.form_DTD['gender']) 2848 _pnl_form.PRW_dob.SetText(self.form_DTD['dob']) 2849 _pnl_form.PRW_lastname.SetText(self.form_DTD['lastnames']) 2850 _pnl_form.PRW_firstname.SetText(self.form_DTD['firstnames']) 2851 _pnl_form.PRW_title.SetText(self.form_DTD['title']) 2852 _pnl_form.PRW_nick.SetText(self.form_DTD['nick']) 2853 _pnl_form.PRW_occupation.SetText(self.form_DTD['occupation']) 2854 _pnl_form.TTC_address_number.SetValue(self.form_DTD['address_number']) 2855 _pnl_form.PRW_street.SetText(self.form_DTD['street']) 2856 _pnl_form.PRW_zip_code.SetText(self.form_DTD['zip_code']) 2857 _pnl_form.PRW_town.SetText(self.form_DTD['town']) 2858 _pnl_form.PRW_state.SetData(self.form_DTD['state']) 2859 _pnl_form.PRW_country.SetData(self.form_DTD['country']) 2860 _pnl_form.TTC_phone.SetValue(self.form_DTD['phone']) 2861 _pnl_form.TCTRL_comment.SetValue(self.form_DTD['comment']) 2862 return True # Prevent wxDialog from complaining
2863 #--------------------------------------------------------
2864 - def TransferFromWindow(self):
2865 """ 2866 Transfer data from window to validator. 2867 The default implementation returns False, indicating that an error 2868 occurred. We simply return True, as we don't do any data transfer. 2869 """ 2870 # FIXME: should be called automatically 2871 if not self.GetWindow().GetParent().Validate(): 2872 return False 2873 try: 2874 _pnl_form = self.GetWindow().GetParent() 2875 # fill in self.form_DTD with values from controls 2876 self.form_DTD['gender'] = _pnl_form.PRW_gender.GetData() 2877 self.form_DTD['dob'] = _pnl_form.PRW_dob.GetData() 2878 2879 self.form_DTD['lastnames'] = _pnl_form.PRW_lastname.GetValue() 2880 self.form_DTD['firstnames'] = _pnl_form.PRW_firstname.GetValue() 2881 self.form_DTD['title'] = _pnl_form.PRW_title.GetValue() 2882 self.form_DTD['nick'] = _pnl_form.PRW_nick.GetValue() 2883 2884 self.form_DTD['occupation'] = _pnl_form.PRW_occupation.GetValue() 2885 2886 self.form_DTD['address_number'] = _pnl_form.TTC_address_number.GetValue() 2887 self.form_DTD['street'] = _pnl_form.PRW_street.GetValue() 2888 self.form_DTD['zip_code'] = _pnl_form.PRW_zip_code.GetValue() 2889 self.form_DTD['town'] = _pnl_form.PRW_town.GetValue() 2890 self.form_DTD['state'] = _pnl_form.PRW_state.GetData() 2891 self.form_DTD['country'] = _pnl_form.PRW_country.GetData() 2892 2893 self.form_DTD['phone'] = _pnl_form.TTC_phone.GetValue() 2894 2895 self.form_DTD['comment'] = _pnl_form.TCTRL_comment.GetValue() 2896 except: 2897 return False 2898 return True
2899 #============================================================
2900 -class cFormDTD:
2901 """ 2902 Simple Data Transfer Dictionary class to make easy the trasfer of 2903 data between the form (view) and the business logic. 2904 2905 Maybe later consider turning this into a standard dict by 2906 {}.fromkeys([key, key, ...], default) when it becomes clear that 2907 we really don't need the added potential of a full-fledged class. 2908 """
2909 - def __init__(self, fields):
2910 """ 2911 Initialize the DTD with the supplied field names. 2912 @param fields The names of the fields. 2913 @type fields A TupleType instance. 2914 """ 2915 self.data = {} 2916 for a_field in fields: 2917 self.data[a_field] = ''
2918
2919 - def __getitem__(self, attribute):
2920 """ 2921 Retrieve the value of the given attribute (key) 2922 @param attribute The attribute (key) to retrieve its value for. 2923 @type attribute a StringType instance. 2924 """ 2925 if not self.data[attribute]: 2926 return '' 2927 return self.data[attribute]
2928
2929 - def __setitem__(self, attribute, value):
2930 """ 2931 Set the value of a given attribute (key). 2932 @param attribute The attribute (key) to set its value for. 2933 @type attribute a StringType instance. 2934 @param avaluee The value to set. 2935 @rtpe attribute a StringType instance. 2936 """ 2937 self.data[attribute] = value
2938
2939 - def __str__(self):
2940 """ 2941 Print string representation of the DTD object. 2942 """ 2943 return str(self.data)
2944 #============================================================ 2945 # patient demographics editing classes 2946 #============================================================
2947 -class cPersonDemographicsEditorNb(wx.Notebook):
2948 """Notebook displaying demographics editing pages: 2949 2950 - Identity 2951 - Contacts (addresses, phone numbers, etc) 2952 2953 Does NOT act on/listen to the current patient. 2954 """ 2955 #--------------------------------------------------------
2956 - def __init__(self, parent, id):
2957 2958 wx.Notebook.__init__ ( 2959 self, 2960 parent = parent, 2961 id = id, 2962 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER, 2963 name = self.__class__.__name__ 2964 ) 2965 2966 self.__identity = None 2967 self.__do_layout() 2968 self.SetSelection(0)
2969 #-------------------------------------------------------- 2970 # public API 2971 #--------------------------------------------------------
2972 - def refresh(self):
2973 """Populate fields in pages with data from model.""" 2974 for page_idx in range(self.GetPageCount()): 2975 page = self.GetPage(page_idx) 2976 page.identity = self.__identity 2977 2978 return True
2979 #-------------------------------------------------------- 2980 # internal API 2981 #--------------------------------------------------------
2982 - def __do_layout(self):
2983 """Build patient edition notebook pages.""" 2984 # contacts page 2985 new_page = cPersonContactsManagerPnl(self, -1) 2986 new_page.identity = self.__identity 2987 self.AddPage ( 2988 page = new_page, 2989 text = _('Contacts'), 2990 select = True 2991 ) 2992 2993 # identity page 2994 new_page = cPersonIdentityManagerPnl(self, -1) 2995 new_page.identity = self.__identity 2996 self.AddPage ( 2997 page = new_page, 2998 text = _('Identity'), 2999 select = False 3000 )
3001 #-------------------------------------------------------- 3002 # properties 3003 #--------------------------------------------------------
3004 - def _get_identity(self):
3005 return self.__identity
3006
3007 - def _set_identity(self, identity):
3008 self.__identity = identity
3009 3010 identity = property(_get_identity, _set_identity)
3011 #============================================================ 3012 # FIXME: support multiple occupations 3013 # FIXME: redo with wxGlade 3014
3015 -class cPatOccupationsPanel(wx.Panel):
3016 """Page containing patient occupations edition fields. 3017 """
3018 - def __init__(self, parent, id, ident=None):
3019 """ 3020 Creates a new instance of BasicPatDetailsPage 3021 @param parent - The parent widget 3022 @type parent - A wx.Window instance 3023 @param id - The widget id 3024 @type id - An integer 3025 """ 3026 wx.Panel.__init__(self, parent, id) 3027 self.__ident = ident 3028 self.__do_layout()
3029 #--------------------------------------------------------
3030 - def __do_layout(self):
3031 PNL_form = wx.Panel(self, -1) 3032 # occupation 3033 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation')) 3034 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1) 3035 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient")) 3036 # known since 3037 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated')) 3038 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY) 3039 3040 # layout input widgets 3041 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4) 3042 SZR_input.AddGrowableCol(1) 3043 SZR_input.Add(STT_occupation, 0, wx.SHAPED) 3044 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND) 3045 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED) 3046 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND) 3047 PNL_form.SetSizerAndFit(SZR_input) 3048 3049 # layout page 3050 SZR_main = wx.BoxSizer(wx.VERTICAL) 3051 SZR_main.Add(PNL_form, 1, wx.EXPAND) 3052 self.SetSizer(SZR_main)
3053 #--------------------------------------------------------
3054 - def set_identity(self, identity):
3055 return self.refresh(identity=identity)
3056 #--------------------------------------------------------
3057 - def refresh(self, identity=None):
3058 if identity is not None: 3059 self.__ident = identity 3060 jobs = self.__ident.get_occupations() 3061 if len(jobs) > 0: 3062 self.PRW_occupation.SetText(jobs[0]['l10n_occupation']) 3063 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y')) 3064 return True
3065 #--------------------------------------------------------
3066 - def save(self):
3067 if self.PRW_occupation.IsModified(): 3068 new_job = self.PRW_occupation.GetValue().strip() 3069 jobs = self.__ident.get_occupations() 3070 for job in jobs: 3071 if job['l10n_occupation'] == new_job: 3072 continue 3073 self.__ident.unlink_occupation(occupation = job['l10n_occupation']) 3074 self.__ident.link_occupation(occupation = new_job) 3075 return True
3076 #============================================================
3077 -class cNotebookedPatEditionPanel(wx.Panel, gmRegetMixin.cRegetOnPaintMixin):
3078 """Patient demographics plugin for main notebook. 3079 3080 Hosts another notebook with pages for Identity, Contacts, etc. 3081 3082 Acts on/listens to the currently active patient. 3083 """ 3084 #--------------------------------------------------------
3085 - def __init__(self, parent, id):
3086 wx.Panel.__init__ (self, parent = parent, id = id, style = wx.NO_BORDER) 3087 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 3088 self.__do_layout() 3089 self.__register_interests()
3090 #-------------------------------------------------------- 3091 # public API 3092 #-------------------------------------------------------- 3093 #-------------------------------------------------------- 3094 # internal helpers 3095 #--------------------------------------------------------
3096 - def __do_layout(self):
3097 """Arrange widgets.""" 3098 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1) 3099 3100 szr_main = wx.BoxSizer(wx.VERTICAL) 3101 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND) 3102 self.SetSizerAndFit(szr_main)
3103 #-------------------------------------------------------- 3104 # event handling 3105 #--------------------------------------------------------
3106 - def __register_interests(self):
3107 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 3108 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
3109 #--------------------------------------------------------
3110 - def _on_pre_patient_selection(self):
3111 self._schedule_data_reget()
3112 #--------------------------------------------------------
3113 - def _on_post_patient_selection(self):
3114 self._schedule_data_reget()
3115 #-------------------------------------------------------- 3116 # reget mixin API 3117 #--------------------------------------------------------
3118 - def _populate_with_data(self):
3119 """Populate fields in pages with data from model.""" 3120 pat = gmPerson.gmCurrentPatient() 3121 if pat.connected: 3122 self.__patient_notebook.identity = pat 3123 else: 3124 self.__patient_notebook.identity = None 3125 self.__patient_notebook.refresh() 3126 return True
3127 #============================================================
3128 -def create_identity_from_dtd(dtd=None):
3129 """ 3130 Register a new patient, given the data supplied in the 3131 Data Transfer Dictionary object. 3132 3133 @param basic_details_DTD Data Transfer Dictionary encapsulating all the 3134 supplied data. 3135 @type basic_details_DTD A cFormDTD instance. 3136 """ 3137 new_identity = gmPerson.create_identity ( 3138 gender = dtd['gender'], 3139 dob = dtd['dob'].get_pydt(), 3140 lastnames = dtd['lastnames'], 3141 firstnames = dtd['firstnames'] 3142 ) 3143 if new_identity is None: 3144 _log.error('cannot create identity from %s' % str(dtd)) 3145 return None 3146 _log.debug('identity created: %s' % new_identity) 3147 3148 if dtd['comment'] is not None: 3149 if dtd['comment'].strip() != u'': 3150 name = new_identity.get_active_name() 3151 name['comment'] = dtd['comment'] 3152 name.save_payload() 3153 3154 return new_identity
3155 #============================================================
3156 -def update_identity_from_dtd(identity, dtd=None):
3157 """ 3158 Update patient details with data supplied by 3159 Data Transfer Dictionary object. 3160 3161 @param basic_details_DTD Data Transfer Dictionary encapsulating all the 3162 supplied data. 3163 @type basic_details_DTD A cFormDTD instance. 3164 """ 3165 # identity 3166 if identity['gender'] != dtd['gender']: 3167 identity['gender'] = dtd['gender'] 3168 if identity['dob'] != dtd['dob'].get_pydt(): 3169 identity['dob'] = dtd['dob'].get_pydt() 3170 if len(dtd['title']) > 0 and identity['title'] != dtd['title']: 3171 identity['title'] = dtd['title'] 3172 # FIXME: error checking 3173 # FIXME: we need a trigger to update the values of the 3174 # view, identity['keys'], eg. lastnames and firstnames 3175 # are not refreshed. 3176 identity.save_payload() 3177 3178 # names 3179 # FIXME: proper handling of "active" 3180 if identity['firstnames'] != dtd['firstnames'] or identity['lastnames'] != dtd['lastnames']: 3181 new_name = identity.add_name(firstnames = dtd['firstnames'], lastnames = dtd['lastnames'], active = True) 3182 # nickname 3183 if len(dtd['nick']) > 0 and identity['preferred'] != dtd['nick']: 3184 identity.set_nickname(nickname = dtd['nick']) 3185 3186 return True
3187 #============================================================ 3231 #============================================================ 3244 #============================================================
3245 -class TestWizardPanel(wx.Panel):
3246 """ 3247 Utility class to test the new patient wizard. 3248 """ 3249 #--------------------------------------------------------
3250 - def __init__(self, parent, id):
3251 """ 3252 Create a new instance of TestPanel. 3253 @param parent The parent widget 3254 @type parent A wx.Window instance 3255 """ 3256 wx.Panel.__init__(self, parent, id) 3257 wizard = cNewPatientWizard(self) 3258 print wizard.RunWizard()
3259 #============================================================ 3260 if __name__ == "__main__": 3261 3262 #--------------------------------------------------------
3263 - def test_zipcode_prw():
3264 app = wx.PyWidgetTester(size = (200, 50)) 3265 pw = cZipcodePhraseWheel(app.frame, -1) 3266 app.frame.Show(True) 3267 app.MainLoop()
3268 #--------------------------------------------------------
3269 - def test_state_prw():
3270 app = wx.PyWidgetTester(size = (200, 50)) 3271 pw = cStateSelectionPhraseWheel(app.frame, -1) 3272 # pw.set_context(context = u'zip', val = u'04318') 3273 # pw.set_context(context = u'country', val = u'Deutschland') 3274 app.frame.Show(True) 3275 app.MainLoop()
3276 #--------------------------------------------------------
3277 - def test_urb_prw():
3278 app = wx.PyWidgetTester(size = (200, 50)) 3279 pw = cUrbPhraseWheel(app.frame, -1) 3280 app.frame.Show(True) 3281 pw.set_context(context = u'zip', val = u'04317') 3282 app.MainLoop()
3283 #--------------------------------------------------------
3284 - def test_suburb_prw():
3285 app = wx.PyWidgetTester(size = (200, 50)) 3286 pw = cSuburbPhraseWheel(app.frame, -1) 3287 app.frame.Show(True) 3288 app.MainLoop()
3289 #--------------------------------------------------------
3290 - def test_address_type_prw():
3291 app = wx.PyWidgetTester(size = (200, 50)) 3292 pw = cAddressTypePhraseWheel(app.frame, -1) 3293 app.frame.Show(True) 3294 app.MainLoop()
3295 #--------------------------------------------------------
3296 - def test_address_prw():
3297 app = wx.PyWidgetTester(size = (200, 50)) 3298 pw = cAddressPhraseWheel(app.frame, -1) 3299 app.frame.Show(True) 3300 app.MainLoop()
3301 #--------------------------------------------------------
3302 - def test_street_prw():
3303 app = wx.PyWidgetTester(size = (200, 50)) 3304 pw = cStreetPhraseWheel(app.frame, -1) 3305 # pw.set_context(context = u'zip', val = u'04318') 3306 app.frame.Show(True) 3307 app.MainLoop()
3308 #--------------------------------------------------------
3309 - def test_organizer_pnl():
3310 app = wx.PyWidgetTester(size = (600, 400)) 3311 app.SetWidget(cKOrganizerSchedulePnl) 3312 app.MainLoop()
3313 #--------------------------------------------------------
3314 - def test_person_names_pnl():
3315 app = wx.PyWidgetTester(size = (600, 400)) 3316 widget = cPersonNamesManagerPnl(app.frame, -1) 3317 widget.identity = activate_patient() 3318 app.frame.Show(True) 3319 app.MainLoop()
3320 #--------------------------------------------------------
3321 - def test_person_ids_pnl():
3322 app = wx.PyWidgetTester(size = (600, 400)) 3323 widget = cPersonIDsManagerPnl(app.frame, -1) 3324 widget.identity = activate_patient() 3325 app.frame.Show(True) 3326 app.MainLoop()
3327 #--------------------------------------------------------
3328 - def test_pat_ids_pnl():
3329 app = wx.PyWidgetTester(size = (600, 400)) 3330 widget = cPersonIdentityManagerPnl(app.frame, -1) 3331 widget.identity = activate_patient() 3332 app.frame.Show(True) 3333 app.MainLoop()
3334 #--------------------------------------------------------
3335 - def test_name_ea_pnl():
3336 app = wx.PyWidgetTester(size = (600, 400)) 3337 app.SetWidget(cNameGenderDOBEditAreaPnl, name = activate_patient().get_active_name()) 3338 app.MainLoop()
3339 #--------------------------------------------------------
3340 - def test_address_ea_pnl():
3341 app = wx.PyWidgetTester(size = (600, 400)) 3342 app.SetWidget(cAddressEditAreaPnl, address = gmDemographicRecord.cAddress(aPK_obj = 1)) 3343 app.MainLoop()
3344 #--------------------------------------------------------
3345 - def test_person_adrs_pnl():
3346 app = wx.PyWidgetTester(size = (600, 400)) 3347 widget = cPersonAddressesManagerPnl(app.frame, -1) 3348 widget.identity = activate_patient() 3349 app.frame.Show(True) 3350 app.MainLoop()
3351 #--------------------------------------------------------
3352 - def test_person_comms_pnl():
3353 app = wx.PyWidgetTester(size = (600, 400)) 3354 widget = cPersonCommsManagerPnl(app.frame, -1) 3355 widget.identity = activate_patient() 3356 app.frame.Show(True) 3357 app.MainLoop()
3358 #--------------------------------------------------------
3359 - def test_pat_contacts_pnl():
3360 app = wx.PyWidgetTester(size = (600, 400)) 3361 widget = cPersonContactsManagerPnl(app.frame, -1) 3362 widget.identity = activate_patient() 3363 app.frame.Show(True) 3364 app.MainLoop()
3365 #--------------------------------------------------------
3366 - def test_cPersonDemographicsEditorNb():
3367 app = wx.PyWidgetTester(size = (600, 400)) 3368 widget = cPersonDemographicsEditorNb(app.frame, -1) 3369 widget.identity = activate_patient() 3370 widget.refresh() 3371 app.frame.Show(True) 3372 app.MainLoop()
3373 #--------------------------------------------------------
3374 - def activate_patient():
3375 patient = gmPerson.ask_for_patient() 3376 if patient is None: 3377 print "No patient. Exiting gracefully..." 3378 sys.exit(0) 3379 from Gnumed.wxpython import gmPatSearchWidgets 3380 gmPatSearchWidgets.set_active_patient(patient=patient) 3381 return patient
3382 #-------------------------------------------------------- 3383 if len(sys.argv) > 1 and sys.argv[1] == 'test': 3384 3385 gmI18N.activate_locale() 3386 gmI18N.install_domain(domain='gnumed') 3387 gmPG2.get_connection() 3388 3389 # a = cFormDTD(fields = cBasicPatDetailsPage.form_fields) 3390 3391 # app = wx.PyWidgetTester(size = (400, 300)) 3392 # app.SetWidget(cNotebookedPatEditionPanel, -1) 3393 # app.SetWidget(TestWizardPanel, -1) 3394 # app.frame.Show(True) 3395 # app.MainLoop() 3396 3397 # phrasewheels 3398 # test_zipcode_prw() 3399 # test_state_prw() 3400 # test_street_prw() 3401 # test_organizer_pnl() 3402 #test_address_type_prw() 3403 #test_suburb_prw() 3404 test_urb_prw() 3405 #test_address_prw() 3406 3407 # contacts related widgets 3408 #test_address_ea_pnl() 3409 #test_person_adrs_pnl() 3410 #test_person_comms_pnl() 3411 #test_pat_contacts_pnl() 3412 3413 # identity related widgets 3414 #test_person_names_pnl() 3415 #test_person_ids_pnl() 3416 #test_pat_ids_pnl() 3417 #test_name_ea_pnl() 3418 3419 #test_cPersonDemographicsEditorNb() 3420 3421 #============================================================ 3422