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

Source Code for Module Gnumed.wxpython.gmPersonCreationWidgets

  1  """GNUmed patient creation widgets. 
  2   
  3  copyright: authors 
  4  """ 
  5  #============================================================ 
  6  __author__ = "K.Hilbert" 
  7  __license__ = "GPL v2 or later (details at http://www.gnu.org)" 
  8   
  9  import logging 
 10  import sys 
 11  import datetime as pydt 
 12   
 13   
 14  import wx 
 15   
 16   
 17  if __name__ == '__main__': 
 18          sys.path.insert(0, '../../') 
 19  from Gnumed.pycommon import gmCfg 
 20  from Gnumed.pycommon import gmPG2 
 21  from Gnumed.pycommon import gmTools 
 22  from Gnumed.pycommon import gmDateTime 
 23  from Gnumed.pycommon import gmDispatcher 
 24   
 25  from Gnumed.business import gmPraxis 
 26  from Gnumed.business import gmPerson 
 27  from Gnumed.business import gmStaff 
 28  from Gnumed.business import gmDemographicRecord 
 29   
 30  from Gnumed.wxpython import gmEditArea 
 31  from Gnumed.wxpython import gmGuiHelpers 
 32  from Gnumed.wxpython.gmDemographicsWidgets import _validate_dob_field, _validate_tob_field 
 33   
 34   
 35  _log = logging.getLogger('gm.patient') 
 36   
 37  #============================================================ 
38 -def create_new_person(parent=None, activate=False):
39 40 dbcfg = gmCfg.cCfgSQL() 41 42 def_region = dbcfg.get2 ( 43 option = u'person.create.default_region', 44 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 45 bias = u'user' 46 ) 47 def_country = None 48 49 if def_region is None: 50 def_country = dbcfg.get2 ( 51 option = u'person.create.default_country', 52 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 53 bias = u'user' 54 ) 55 else: 56 countries = gmDemographicRecord.get_country_for_region(region = def_region) 57 if len(countries) == 1: 58 def_country = countries[0]['code_country'] 59 60 if parent is None: 61 parent = wx.GetApp().GetTopWindow() 62 63 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region) 64 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) 65 dlg.SetTitle(_('Adding new person')) 66 ea._PRW_lastname.SetFocus() 67 result = dlg.ShowModal() 68 pat = ea.data 69 dlg.Destroy() 70 71 if result != wx.ID_OK: 72 return False 73 74 _log.debug('created new person [%s]', pat.ID) 75 76 if activate: 77 from Gnumed.wxpython import gmPatSearchWidgets 78 gmPatSearchWidgets.set_active_patient(patient = pat) 79 80 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin') 81 82 return True
83 84 #============================================================ 85 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl 86
87 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
88
89 - def __init__(self, *args, **kwargs):
90 91 try: 92 self.default_region = kwargs['region'] 93 del kwargs['region'] 94 except KeyError: 95 self.default_region = None 96 97 try: 98 self.default_country = kwargs['country'] 99 del kwargs['country'] 100 except KeyError: 101 self.default_country = None 102 103 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs) 104 gmEditArea.cGenericEditAreaMixin.__init__(self) 105 106 self.mode = 'new' 107 self.data = None 108 self._address = None 109 110 self.__init_ui() 111 self.__register_interests()
112 #---------------------------------------------------------------- 113 # internal helpers 114 #----------------------------------------------------------------
115 - def __init_ui(self):
116 self._PRW_lastname.final_regex = '.+' 117 self._PRW_firstnames.final_regex = '.+' 118 self._PRW_address_searcher.selection_only = False 119 120 # only if we would support None on selection_only's: 121 # self._PRW_external_id_type.selection_only = True 122 123 if self.default_country is not None: 124 match = self._PRW_country._data2match(data = self.default_country) 125 if match is not None: 126 self._PRW_country.SetText(value = match['field_label'], data = match['data']) 127 128 if self.default_region is not None: 129 self._PRW_region.SetText(value = self.default_region) 130 131 self._PRW_type.SetText(value = u'home') 132 # FIXME: only use this if member of gm-doctors, 133 # FIXME: other than that check fallback_primary_provider 134 self._PRW_primary_provider.SetData(data = gmStaff.gmCurrentProvider()['pk_staff']) 135 136 self._PRW_lastname.SetFocus()
137 #----------------------------------------------------------------
138 - def _refresh_ext_id_warning(self):
139 id_type = self._PRW_external_id_type.GetData() 140 if id_type is None: 141 self._LBL_id_exists.SetLabel(u'') 142 return 143 val = self._TCTRL_external_id_value.GetValue().strip() 144 if val == u'': 145 self._LBL_id_exists.SetLabel(u'') 146 return 147 if gmPerson.external_id_exists(pk_issuer = id_type, value = val) > 0: 148 self._LBL_id_exists.SetLabel(_('ID exists !')) 149 else: 150 self._LBL_id_exists.SetLabel(u'')
151 #----------------------------------------------------------------
152 - def _refresh_dupe_warning(self):
153 lname = self._PRW_lastname.GetValue().strip() 154 if lname == u'': 155 self._LBL_person_exists.SetLabel(u'') 156 return 157 158 dob = self._PRW_dob.GetData() 159 if dob is None: 160 self._LBL_person_exists.SetLabel(u'') 161 return 162 163 fname = gmTools.none_if(self._PRW_firstnames.GetValue().strip()[:1], u'') 164 165 no_of_dupes = gmPerson.person_exists(lastnames = lname, firstnames = fname, dob = dob) 166 if no_of_dupes == 0: 167 lbl = u'' 168 elif no_of_dupes == 1: 169 lbl = _('One "%s, %s (%s)" already exists !') % ( 170 lname, 171 gmTools.coalesce(fname, u'?', u'%s %%s. %s' % (gmTools.u_ellipsis, gmTools.u_ellipsis)), 172 gmDateTime.pydt_strftime(dob, '%Y %b %d', 'utf8') 173 ) 174 else: 175 lbl = _('%s "%s, %s (%s)" already exist !') % ( 176 no_of_dupes, 177 lname, 178 gmTools.coalesce(fname, u'?', u'%s %%s. %s' % (gmTools.u_ellipsis, gmTools.u_ellipsis)), 179 gmDateTime.pydt_strftime(dob, '%Y %b %d', 'utf8') 180 ) 181 182 self._LBL_person_exists.SetLabel(lbl)
183 #----------------------------------------------------------------
184 - def __perhaps_invalidate_address_searcher(self, ctrl=None, field=None):
185 186 adr = self._PRW_address_searcher.address 187 if adr is None: 188 return True 189 190 if ctrl.GetValue().strip() != adr[field]: 191 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None) 192 return True 193 194 return False
195 #----------------------------------------------------------------
197 adr = self._PRW_address_searcher.address 198 if adr is None: 199 return True 200 201 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode']) 202 203 self._PRW_street.SetText(value = adr['street'], data = adr['street']) 204 self._PRW_street.set_context(context = u'zip', val = adr['postcode']) 205 206 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb']) 207 self._PRW_urb.set_context(context = u'zip', val = adr['postcode']) 208 209 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state']) 210 self._PRW_region.set_context(context = u'zip', val = adr['postcode']) 211 212 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country']) 213 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
214 #----------------------------------------------------------------
216 error = False 217 218 # name fields 219 if self._PRW_lastname.GetValue().strip() == u'': 220 error = True 221 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.')) 222 self._PRW_lastname.display_as_valid(False) 223 else: 224 self._PRW_lastname.display_as_valid(True) 225 226 if self._PRW_firstnames.GetValue().strip() == '': 227 error = True 228 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.')) 229 self._PRW_firstnames.display_as_valid(False) 230 else: 231 self._PRW_firstnames.display_as_valid(True) 232 233 # gender 234 if self._PRW_gender.GetData() is None: 235 error = True 236 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.')) 237 self._PRW_gender.display_as_valid(False) 238 else: 239 self._PRW_gender.display_as_valid(True) 240 241 # dob validation 242 if not _validate_dob_field(self._PRW_dob): 243 error = True 244 245 # TOB validation 246 if _validate_tob_field(self._TCTRL_tob): 247 self.display_ctrl_as_valid(ctrl = self._TCTRL_tob, valid = True) 248 else: 249 error = True 250 self.display_ctrl_as_valid(ctrl = self._TCTRL_tob, valid = False) 251 252 return (not error)
253 #----------------------------------------------------------------
254 - def __address_valid_for_save(self, empty_address_is_valid=False):
255 256 # existing address ? if so set other fields 257 if self._PRW_address_searcher.GetData() is not None: 258 wx.CallAfter(self.__set_fields_from_address_searcher) 259 return True 260 261 # must either all contain something or none of them 262 fields_to_fill = ( 263 self._TCTRL_number, 264 self._PRW_zip, 265 self._PRW_street, 266 self._PRW_urb, 267 self._PRW_type 268 ) 269 no_of_filled_fields = 0 270 271 for field in fields_to_fill: 272 if field.GetValue().strip() != u'': 273 no_of_filled_fields += 1 274 field.display_as_valid(True) 275 276 # empty address ? 277 if no_of_filled_fields == 0: 278 if empty_address_is_valid: 279 return True 280 else: 281 return None 282 283 # incompletely filled address ? 284 if no_of_filled_fields != len(fields_to_fill): 285 for field in fields_to_fill: 286 if field.GetValue().strip() == u'': 287 field.display_as_valid(False) 288 field.SetFocus() 289 msg = _('To properly create an address, all the related fields must be filled in.') 290 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 291 return False 292 293 # fields which must contain a selected item 294 # FIXME: they must also contain an *acceptable combination* which 295 # FIXME: can only be tested against the database itself ... 296 strict_fields = ( 297 self._PRW_type, 298 self._PRW_region, 299 self._PRW_country 300 ) 301 error = False 302 for field in strict_fields: 303 if field.GetData() is None: 304 error = True 305 field.display_as_valid(False) 306 field.SetFocus() 307 else: 308 field.display_as_valid(True) 309 310 if error: 311 msg = _('This field must contain an item selected from the dropdown list.') 312 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 313 return False 314 315 return True
316 #----------------------------------------------------------------
317 - def __register_interests(self):
318 319 # identity 320 self._PRW_lastname.add_callback_on_lose_focus(self._on_leaving_lastname) 321 self._PRW_firstnames.add_callback_on_lose_focus(self._on_leaving_firstname) 322 self._PRW_dob.add_callback_on_lose_focus(self._on_leaving_dob) 323 324 # address 325 self._PRW_address_searcher.add_callback_on_lose_focus(self._on_leaving_adress_searcher) 326 327 # invalidate address searcher when any field edited 328 self._PRW_street.add_callback_on_lose_focus(self._invalidate_address_searcher) 329 wx.EVT_KILL_FOCUS(self._TCTRL_number, self._on_leaving_number) 330 wx.EVT_KILL_FOCUS(self._TCTRL_unit, self._on_leaving_unit) 331 self._PRW_urb.add_callback_on_lose_focus(self._invalidate_address_searcher) 332 self._PRW_region.add_callback_on_lose_focus(self._invalidate_address_searcher) 333 334 self._PRW_zip.add_callback_on_lose_focus(self._on_leaving_zip) 335 self._PRW_country.add_callback_on_lose_focus(self._on_leaving_country) 336 337 self._PRW_external_id_type.add_callback_on_lose_focus(callback = self._on_leaving_ext_id_type) 338 self._TCTRL_external_id_value.add_callback_on_lose_focus(callback = self._on_leaving_ext_id_val)
339 340 #---------------------------------------------------------------- 341 # event handlers 342 #----------------------------------------------------------------
343 - def _on_leaving_ext_id_type(self):
344 wx.CallAfter(self._refresh_ext_id_warning)
345 #----------------------------------------------------------------
346 - def _on_leaving_ext_id_val(self):
347 wx.CallAfter(self._refresh_ext_id_warning)
348 #----------------------------------------------------------------
349 - def _on_leaving_lastname(self):
350 wx.CallAfter(self._refresh_dupe_warning)
351 #----------------------------------------------------------------
352 - def _on_leaving_firstname(self):
353 """Set the gender according to entered firstname. 354 355 Matches are fetched from existing records in backend. 356 """ 357 wx.CallAfter(self._refresh_dupe_warning) 358 359 # only set if not already set so as to not 360 # overwrite a change by the user 361 if self._PRW_gender.GetData() is not None: 362 return True 363 364 firstname = self._PRW_firstnames.GetValue().strip() 365 if firstname == u'': 366 return True 367 368 gender = gmPerson.map_firstnames2gender(firstnames = firstname) 369 if gender is None: 370 return True 371 372 wx.CallAfter(self._PRW_gender.SetData, gender) 373 374 return True
375 #----------------------------------------------------------------
376 - def _on_leaving_dob(self):
377 wx.CallAfter(self._refresh_dupe_warning)
378 #----------------------------------------------------------------
379 - def _on_leaving_zip(self):
380 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode') 381 382 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'') 383 self._PRW_street.set_context(context = u'zip', val = zip_code) 384 self._PRW_urb.set_context(context = u'zip', val = zip_code) 385 self._PRW_region.set_context(context = u'zip', val = zip_code) 386 self._PRW_country.set_context(context = u'zip', val = zip_code) 387 388 return True
389 #----------------------------------------------------------------
390 - def _on_leaving_country(self):
391 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country') 392 393 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'') 394 self._PRW_region.set_context(context = u'country', val = country) 395 396 return True
397 #----------------------------------------------------------------
398 - def _on_leaving_number(self, evt):
399 if self._TCTRL_number.GetValue().strip() == u'': 400 adr = self._PRW_address_searcher.address 401 if adr is None: 402 return True 403 self._TCTRL_number.SetValue(adr['number']) 404 return True 405 406 self.__perhaps_invalidate_address_searcher(self._TCTRL_number, 'number') 407 return True
408 #----------------------------------------------------------------
409 - def _on_leaving_unit(self, evt):
410 if self._TCTRL_unit.GetValue().strip() == u'': 411 adr = self._PRW_address_searcher.address 412 if adr is None: 413 return True 414 self._TCTRL_unit.SetValue(gmTools.coalesce(adr['subunit'], u'')) 415 return True 416 417 self.__perhaps_invalidate_address_searcher(self._TCTRL_unit, 'subunit') 418 return True
419 #----------------------------------------------------------------
420 - def _invalidate_address_searcher(self, *args, **kwargs):
421 mapping = [ 422 (self._PRW_street, 'street'), 423 (self._PRW_urb, 'urb'), 424 (self._PRW_region, 'l10n_state') 425 ] 426 # loop through fields and invalidate address searcher if different 427 for ctrl, field in mapping: 428 if self.__perhaps_invalidate_address_searcher(ctrl, field): 429 return True 430 431 return True
432 #----------------------------------------------------------------
434 if self._PRW_address_searcher.address is None: 435 return True 436 437 wx.CallAfter(self.__set_fields_from_address_searcher) 438 return True
439 #---------------------------------------------------------------- 440 # generic Edit Area mixin API 441 #----------------------------------------------------------------
442 - def _valid_for_save(self):
443 if self._PRW_primary_provider.GetValue().strip() == u'': 444 self._PRW_primary_provider.display_as_valid(True) 445 else: 446 if self._PRW_primary_provider.GetData() is None: 447 self._PRW_primary_provider.display_as_valid(False) 448 else: 449 self._PRW_primary_provider.display_as_valid(True) 450 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
451 #----------------------------------------------------------------
452 - def _save_as_new(self):
453 454 if self._PRW_dob.GetValue().strip() == u'': 455 if not _empty_dob_allowed(): 456 self._PRW_dob.display_as_valid(False) 457 self._PRW_dob.SetFocus() 458 return False 459 460 # identity 461 new_identity = gmPerson.create_identity ( 462 gender = self._PRW_gender.GetData(), 463 dob = self._PRW_dob.GetData(), 464 lastnames = self._PRW_lastname.GetValue().strip(), 465 firstnames = self._PRW_firstnames.GetValue().strip() 466 ) 467 _log.debug('identity created: %s' % new_identity) 468 469 new_identity['dob_is_estimated'] = self._CHBOX_estimated_dob.GetValue() 470 val = self._TCTRL_tob.GetValue().strip() 471 if val != u'': 472 new_identity['tob'] = pydt.time(int(val[:2]), int(val[3:5])) 473 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip()) 474 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u'')) 475 476 prov = self._PRW_primary_provider.GetData() 477 if prov is not None: 478 new_identity['pk_primary_provider'] = prov 479 new_identity['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 480 new_identity.save() 481 482 # address 483 # if we reach this the address cannot be completely empty 484 is_valid = self.__address_valid_for_save(empty_address_is_valid = False) 485 if is_valid is True: 486 # because we currently only check for non-emptiness 487 # we must still deal with database errors 488 try: 489 new_identity.link_address ( 490 number = self._TCTRL_number.GetValue().strip(), 491 street = self._PRW_street.GetValue().strip(), 492 postcode = self._PRW_zip.GetValue().strip(), 493 urb = self._PRW_urb.GetValue().strip(), 494 state = self._PRW_region.GetData(), 495 country = self._PRW_country.GetData(), 496 subunit = gmTools.none_if(self._TCTRL_unit.GetValue().strip(), u''), 497 id_type = self._PRW_type.GetData() 498 ) 499 except gmPG2.dbapi.InternalError: 500 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip()) 501 _log.debug('(sub)unit: >>%s<<', self._TCTRL_unit.GetValue().strip()) 502 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip()) 503 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip()) 504 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip()) 505 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip()) 506 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip()) 507 _log.exception('cannot link address') 508 gmGuiHelpers.gm_show_error ( 509 aTitle = _('Saving address'), 510 aMessage = _( 511 'Cannot save this address.\n' 512 '\n' 513 'You will have to add it via the Demographics plugin.\n' 514 ) 515 ) 516 elif is_valid is False: 517 gmGuiHelpers.gm_show_error ( 518 aTitle = _('Saving address'), 519 aMessage = _( 520 'Address not saved.\n' 521 '\n' 522 'You will have to add it via the Demographics plugin.\n' 523 ) 524 ) 525 # else it is None which means empty address which we ignore 526 527 # phone 528 channel_name = self._PRW_channel_type.GetValue().strip() 529 pk_channel_type = self._PRW_channel_type.GetData() 530 if pk_channel_type is None: 531 if channel_name == u'': 532 channel_name = u'homephone' 533 new_identity.link_comm_channel ( 534 comm_medium = channel_name, 535 pk_channel_type = pk_channel_type, 536 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''), 537 is_confidential = False 538 ) 539 540 # external ID 541 pk_type = self._PRW_external_id_type.GetData() 542 id_value = self._TCTRL_external_id_value.GetValue().strip() 543 if (pk_type is not None) and (id_value != u''): 544 new_identity.add_external_id(value = id_value, pk_type = pk_type) 545 546 # occupation 547 new_identity.link_occupation ( 548 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'') 549 ) 550 551 self.data = new_identity 552 return True
553 #----------------------------------------------------------------
554 - def _save_as_update(self):
555 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
556 #----------------------------------------------------------------
557 - def _refresh_as_new(self):
558 # FIXME: button "empty out" 559 return
560 #----------------------------------------------------------------
561 - def _refresh_from_existing(self):
562 return # there is no forward button so nothing to do here
563 #----------------------------------------------------------------
565 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
566 567 #============================================================ 568 # main 569 #------------------------------------------------------------ 570 if __name__ == "__main__": 571 572 if len(sys.argv) < 2: 573 sys.exit() 574 575 if sys.argv[1] != u'test': 576 sys.exit() 577 578 # from Gnumed.pycommon import gmPG2 579 # from Gnumed.pycommon import gmI18N 580 # gmI18N.activate_locale() 581 # gmI18N.install_domain() 582 583 #-------------------------------------------------------- 584 #test_org_unit_prw() 585