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 sys, os, codecs, re as regex, 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 gmPhraseWheel, gmGuiHelpers, gmDateTimeInput 
  22  from Gnumed.wxpython import gmRegetMixin, gmDataMiningWidgets, gmListWidgets, gmEditArea 
  23  from Gnumed.wxpython import gmAuthWidgets, gmPersonContactWidgets 
  24   
  25   
  26  # constant defs 
  27  _log = logging.getLogger('gm.ui') 
  28   
  29   
  30  try: 
  31          _('dummy-no-need-to-translate-but-make-epydoc-happy') 
  32  except NameError: 
  33          _ = lambda x:x 
  34   
  35  #============================================================ 
  36  #============================================================ 
37 -class cKOrganizerSchedulePnl(gmDataMiningWidgets.cPatientListingPnl):
38
39 - def __init__(self, *args, **kwargs):
40 41 kwargs['message'] = _("Today's KOrganizer appointments ...") 42 kwargs['button_defs'] = [ 43 {'label': _('Reload'), 'tooltip': _('Reload appointments from KOrganizer')}, 44 {'label': u''}, 45 {'label': u''}, 46 {'label': u''}, 47 {'label': u'KOrganizer', 'tooltip': _('Launch KOrganizer')} 48 ] 49 gmDataMiningWidgets.cPatientListingPnl.__init__(self, *args, **kwargs) 50 51 self.fname = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp', 'korganizer2gnumed.csv')) 52 self.reload_cmd = 'konsolekalendar --view --export-type csv --export-file %s' % self.fname
53 54 #--------------------------------------------------------
55 - def _on_BTN_1_pressed(self, event):
56 """Reload appointments from KOrganizer.""" 57 self.reload_appointments()
58 #--------------------------------------------------------
59 - def _on_BTN_5_pressed(self, event):
60 """Reload appointments from KOrganizer.""" 61 found, cmd = gmShellAPI.detect_external_binary(binary = 'korganizer') 62 63 if not found: 64 gmDispatcher.send(signal = 'statustext', msg = _('KOrganizer is not installed.'), beep = True) 65 return 66 67 gmShellAPI.run_command_in_shell(command = cmd, blocking = False)
68 #--------------------------------------------------------
69 - def reload_appointments(self):
70 try: os.remove(self.fname) 71 except OSError: pass 72 gmShellAPI.run_command_in_shell(command=self.reload_cmd, blocking=True) 73 try: 74 csv_file = codecs.open(self.fname , mode = 'rU', encoding = 'utf8', errors = 'replace') 75 except IOError: 76 gmDispatcher.send(signal = u'statustext', msg = _('Cannot access KOrganizer transfer file [%s]') % self.fname, beep = True) 77 return 78 79 csv_lines = gmTools.unicode_csv_reader ( 80 csv_file, 81 delimiter = ',' 82 ) 83 # start_date, start_time, end_date, end_time, title (patient), ort, comment, UID 84 self._LCTRL_items.set_columns ([ 85 _('Place'), 86 _('Start'), 87 u'', 88 u'', 89 _('Patient'), 90 _('Comment') 91 ]) 92 items = [] 93 data = [] 94 for line in csv_lines: 95 items.append([line[5], line[0], line[1], line[3], line[4], line[6]]) 96 data.append([line[4], line[7]]) 97 98 self._LCTRL_items.set_string_items(items = items) 99 self._LCTRL_items.set_column_widths() 100 self._LCTRL_items.set_data(data = data) 101 self._LCTRL_items.patient_key = 0
102 #-------------------------------------------------------- 103 # notebook plugins API 104 #--------------------------------------------------------
105 - def repopulate_ui(self):
106 self.reload_appointments()
107 #============================================================ 108 # occupation related widgets / functions 109 #============================================================
110 -def edit_occupation():
111 112 pat = gmPerson.gmCurrentPatient() 113 curr_jobs = pat.get_occupations() 114 if len(curr_jobs) > 0: 115 old_job = curr_jobs[0]['l10n_occupation'] 116 update = curr_jobs[0]['modified_when'].strftime('%m/%Y') 117 else: 118 old_job = u'' 119 update = u'' 120 121 msg = _( 122 'Please enter the primary occupation of the patient.\n' 123 '\n' 124 'Currently recorded:\n' 125 '\n' 126 ' %s (last updated %s)' 127 ) % (old_job, update) 128 129 new_job = wx.GetTextFromUser ( 130 message = msg, 131 caption = _('Editing primary occupation'), 132 default_value = old_job, 133 parent = None 134 ) 135 if new_job.strip() == u'': 136 return 137 138 for job in curr_jobs: 139 # unlink all but the new job 140 if job['l10n_occupation'] != new_job: 141 pat.unlink_occupation(occupation = job['l10n_occupation']) 142 # and link the new one 143 pat.link_occupation(occupation = new_job)
144 145 #------------------------------------------------------------
146 -class cOccupationPhraseWheel(gmPhraseWheel.cPhraseWheel):
147
148 - def __init__(self, *args, **kwargs):
149 query = u"select distinct name, _(name) from dem.occupation where _(name) %(fragment_condition)s" 150 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 151 mp.setThresholds(1, 3, 5) 152 gmPhraseWheel.cPhraseWheel.__init__ ( 153 self, 154 *args, 155 **kwargs 156 ) 157 self.SetToolTipString(_("Type or select an occupation.")) 158 self.capitalisation_mode = gmTools.CAPS_FIRST 159 self.matcher = mp
160 161 #============================================================ 162 # identity widgets / functions 163 #============================================================
164 -def disable_identity(identity=None):
165 # ask user for assurance 166 go_ahead = gmGuiHelpers.gm_show_question ( 167 _('Are you sure you really, positively want\n' 168 'to disable the following person ?\n' 169 '\n' 170 ' %s %s %s\n' 171 ' born %s\n' 172 '\n' 173 '%s\n' 174 ) % ( 175 identity['firstnames'], 176 identity['lastnames'], 177 identity['gender'], 178 identity['dob'], 179 gmTools.bool2subst ( 180 identity.is_patient, 181 _('This patient DID receive care.'), 182 _('This person did NOT receive care.') 183 ) 184 ), 185 _('Disabling person') 186 ) 187 if not go_ahead: 188 return True 189 190 # get admin connection 191 conn = gmAuthWidgets.get_dbowner_connection ( 192 procedure = _('Disabling patient') 193 ) 194 # - user cancelled 195 if conn is False: 196 return True 197 # - error 198 if conn is None: 199 return False 200 201 # now disable patient 202 gmPG2.run_rw_queries(queries = [{'cmd': u"update dem.identity set deleted=True where pk=%s", 'args': [identity['pk_identity']]}]) 203 204 return True
205 206 #------------------------------------------------------------ 207 # phrasewheels 208 #------------------------------------------------------------
209 -class cLastnamePhraseWheel(gmPhraseWheel.cPhraseWheel):
210
211 - def __init__(self, *args, **kwargs):
212 query = u"select distinct lastnames, lastnames from dem.names where lastnames %(fragment_condition)s order by lastnames limit 25" 213 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 214 mp.setThresholds(3, 5, 9) 215 gmPhraseWheel.cPhraseWheel.__init__ ( 216 self, 217 *args, 218 **kwargs 219 ) 220 self.SetToolTipString(_("Type or select a last name (family name/surname).")) 221 self.capitalisation_mode = gmTools.CAPS_NAMES 222 self.matcher = mp
223 #------------------------------------------------------------
224 -class cFirstnamePhraseWheel(gmPhraseWheel.cPhraseWheel):
225
226 - def __init__(self, *args, **kwargs):
227 query = u""" 228 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20) 229 union 230 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)""" 231 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 232 mp.setThresholds(3, 5, 9) 233 gmPhraseWheel.cPhraseWheel.__init__ ( 234 self, 235 *args, 236 **kwargs 237 ) 238 self.SetToolTipString(_("Type or select a first name (forename/Christian name/given name).")) 239 self.capitalisation_mode = gmTools.CAPS_NAMES 240 self.matcher = mp
241 #------------------------------------------------------------
242 -class cNicknamePhraseWheel(gmPhraseWheel.cPhraseWheel):
243
244 - def __init__(self, *args, **kwargs):
245 query = u""" 246 (select distinct preferred, preferred from dem.names where preferred %(fragment_condition)s order by preferred limit 20) 247 union 248 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20) 249 union 250 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)""" 251 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 252 mp.setThresholds(3, 5, 9) 253 gmPhraseWheel.cPhraseWheel.__init__ ( 254 self, 255 *args, 256 **kwargs 257 ) 258 self.SetToolTipString(_("Type or select an alias (nick name, preferred name, call name, warrior name, artist name).")) 259 # nicknames CAN start with lower case ! 260 #self.capitalisation_mode = gmTools.CAPS_NAMES 261 self.matcher = mp
262 #------------------------------------------------------------
263 -class cTitlePhraseWheel(gmPhraseWheel.cPhraseWheel):
264
265 - def __init__(self, *args, **kwargs):
266 query = u"select distinct title, title from dem.identity where title %(fragment_condition)s" 267 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 268 mp.setThresholds(1, 3, 9) 269 gmPhraseWheel.cPhraseWheel.__init__ ( 270 self, 271 *args, 272 **kwargs 273 ) 274 self.SetToolTipString(_("Type or select a title. Note that the title applies to the person, not to a particular name !")) 275 self.matcher = mp
276 #------------------------------------------------------------
277 -class cGenderSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
278 """Let user select a gender.""" 279 280 _gender_map = None 281
282 - def __init__(self, *args, **kwargs):
283 284 if cGenderSelectionPhraseWheel._gender_map is None: 285 cmd = u""" 286 select tag, l10n_label, sort_weight 287 from dem.v_gender_labels 288 order by sort_weight desc""" 289 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True) 290 cGenderSelectionPhraseWheel._gender_map = {} 291 for gender in rows: 292 cGenderSelectionPhraseWheel._gender_map[gender[idx['tag']]] = { 293 'data': gender[idx['tag']], 294 'label': gender[idx['l10n_label']], 295 'weight': gender[idx['sort_weight']] 296 } 297 298 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = cGenderSelectionPhraseWheel._gender_map.values()) 299 mp.setThresholds(1, 1, 3) 300 301 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 302 self.selection_only = True 303 self.matcher = mp 304 self.picklist_delay = 50
305 #------------------------------------------------------------
306 -class cExternalIDTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
307
308 - def __init__(self, *args, **kwargs):
309 query = u""" 310 select distinct pk, (name || coalesce(' (%s ' || issuer || ')', '')) as label 311 from dem.enum_ext_id_types 312 where name %%(fragment_condition)s 313 order by label limit 25""" % _('issued by') 314 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 315 mp.setThresholds(1, 3, 5) 316 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 317 self.SetToolTipString(_("Enter or select a type for the external ID.")) 318 self.matcher = mp
319 #------------------------------------------------------------
320 -class cExternalIDIssuerPhraseWheel(gmPhraseWheel.cPhraseWheel):
321
322 - def __init__(self, *args, **kwargs):
323 query = u""" 324 select distinct issuer, issuer 325 from dem.enum_ext_id_types 326 where issuer %(fragment_condition)s 327 order by issuer limit 25""" 328 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 329 mp.setThresholds(1, 3, 5) 330 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 331 self.SetToolTipString(_("Type or select an ID issuer.")) 332 self.capitalisation_mode = gmTools.CAPS_FIRST 333 self.matcher = mp
334 #------------------------------------------------------------ 335 # edit areas 336 #------------------------------------------------------------ 337 from Gnumed.wxGladeWidgets import wxgExternalIDEditAreaPnl 338
339 -class cExternalIDEditAreaPnl(wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl):
340 """An edit area for editing/creating external IDs. 341 342 Does NOT act on/listen to the current patient. 343 """
344 - def __init__(self, *args, **kwargs):
345 346 try: 347 self.ext_id = kwargs['external_id'] 348 del kwargs['external_id'] 349 except: 350 self.ext_id = None 351 352 wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl.__init__(self, *args, **kwargs) 353 354 self.identity = None 355 356 self.__register_events() 357 358 self.refresh()
359 #-------------------------------------------------------- 360 # external API 361 #--------------------------------------------------------
362 - def refresh(self, ext_id=None):
363 if ext_id is not None: 364 self.ext_id = ext_id 365 366 if self.ext_id is not None: 367 self._PRW_type.SetText(value = self.ext_id['name'], data = self.ext_id['pk_type']) 368 self._TCTRL_value.SetValue(self.ext_id['value']) 369 self._PRW_issuer.SetText(self.ext_id['issuer']) 370 self._TCTRL_comment.SetValue(gmTools.coalesce(self.ext_id['comment'], u''))
371 # FIXME: clear fields 372 # else: 373 # pass 374 #--------------------------------------------------------
375 - def save(self):
376 377 if not self.__valid_for_save(): 378 return False 379 380 # strip out " (issued by ...)" added by phrasewheel 381 type = regex.split(' \(%s .+\)$' % _('issued by'), self._PRW_type.GetValue().strip(), 1)[0] 382 383 # add new external ID 384 if self.ext_id is None: 385 self.identity.add_external_id ( 386 type_name = type, 387 value = self._TCTRL_value.GetValue().strip(), 388 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''), 389 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 390 ) 391 # edit old external ID 392 else: 393 self.identity.update_external_id ( 394 pk_id = self.ext_id['pk_id'], 395 type = type, 396 value = self._TCTRL_value.GetValue().strip(), 397 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''), 398 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 399 ) 400 401 return True
402 #-------------------------------------------------------- 403 # internal helpers 404 #--------------------------------------------------------
405 - def __register_events(self):
406 self._PRW_type.add_callback_on_lose_focus(self._on_type_set)
407 #--------------------------------------------------------
408 - def _on_type_set(self):
409 """Set the issuer according to the selected type. 410 411 Matches are fetched from existing records in backend. 412 """ 413 pk_curr_type = self._PRW_type.GetData() 414 if pk_curr_type is None: 415 return True 416 rows, idx = gmPG2.run_ro_queries(queries = [{ 417 'cmd': u"select issuer from dem.enum_ext_id_types where pk = %s", 418 'args': [pk_curr_type] 419 }]) 420 if len(rows) == 0: 421 return True 422 wx.CallAfter(self._PRW_issuer.SetText, rows[0][0]) 423 return True
424 #--------------------------------------------------------
425 - def __valid_for_save(self):
426 427 no_errors = True 428 429 # do not test .GetData() because adding external IDs 430 # will create types if necessary 431 # if self._PRW_type.GetData() is None: 432 if self._PRW_type.GetValue().strip() == u'': 433 self._PRW_type.SetBackgroundColour('pink') 434 self._PRW_type.SetFocus() 435 self._PRW_type.Refresh() 436 else: 437 self._PRW_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 438 self._PRW_type.Refresh() 439 440 if self._TCTRL_value.GetValue().strip() == u'': 441 self._TCTRL_value.SetBackgroundColour('pink') 442 self._TCTRL_value.SetFocus() 443 self._TCTRL_value.Refresh() 444 no_errors = False 445 else: 446 self._TCTRL_value.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 447 self._TCTRL_value.Refresh() 448 449 return no_errors
450 #------------------------------------------------------------ 451 from Gnumed.wxGladeWidgets import wxgIdentityEAPnl 452
453 -class cIdentityEAPnl(wxgIdentityEAPnl.wxgIdentityEAPnl, gmEditArea.cGenericEditAreaMixin):
454 """An edit area for editing/creating title/gender/dob/dod etc.""" 455
456 - def __init__(self, *args, **kwargs):
457 458 try: 459 data = kwargs['identity'] 460 del kwargs['identity'] 461 except KeyError: 462 data = None 463 464 wxgIdentityEAPnl.wxgIdentityEAPnl.__init__(self, *args, **kwargs) 465 gmEditArea.cGenericEditAreaMixin.__init__(self) 466 467 self.mode = 'new' 468 self.data = data 469 if data is not None: 470 self.mode = 'edit'
471 472 # self.__init_ui() 473 #---------------------------------------------------------------- 474 # def __init_ui(self): 475 # # adjust phrasewheels etc 476 #---------------------------------------------------------------- 477 # generic Edit Area mixin API 478 #----------------------------------------------------------------
479 - def _valid_for_save(self):
480 481 has_error = False 482 483 if self._PRW_gender.GetData() is None: 484 self._PRW_gender.SetFocus() 485 has_error = True 486 487 if not self._PRW_dob.is_valid_timestamp(): 488 val = self._PRW_dob.GetValue().strip() 489 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val) 490 self._PRW_dob.SetBackgroundColour('pink') 491 self._PRW_dob.Refresh() 492 self._PRW_dob.SetFocus() 493 has_error = True 494 else: 495 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 496 self._PRW_dob.Refresh() 497 498 if not self._DP_dod.is_valid_timestamp(allow_none=True): 499 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.')) 500 self._DP_dod.SetFocus() 501 has_error = True 502 503 return (has_error is False)
504 #----------------------------------------------------------------
505 - def _save_as_new(self):
506 # not intended to be used 507 return False
508 #----------------------------------------------------------------
509 - def _save_as_update(self):
510 511 self.data['gender'] = self._PRW_gender.GetData() 512 513 if self._PRW_dob.GetValue().strip() == u'': 514 self.data['dob'] = None 515 else: 516 self.data['dob'] = self._PRW_dob.GetData().get_pydt() 517 518 self.data['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'') 519 self.data['deceased'] = self._DP_dod.GetValue(as_pydt = True) 520 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 521 522 self.data.save() 523 return True
524 #----------------------------------------------------------------
525 - def _refresh_as_new(self):
526 pass
527 #----------------------------------------------------------------
528 - def _refresh_from_existing(self):
529 530 self._LBL_info.SetLabel(u'ID: #%s' % ( 531 self.data.ID 532 # FIXME: add 'deleted' status 533 )) 534 self._PRW_dob.SetText ( 535 value = self.data.get_formatted_dob(format = '%Y-%m-%d %H:%M', encoding = gmI18N.get_encoding()), 536 data = self.data['dob'] 537 ) 538 self._DP_dod.SetValue(self.data['deceased']) 539 self._PRW_gender.SetData(self.data['gender']) 540 #self._PRW_ethnicity.SetValue() 541 self._PRW_title.SetText(gmTools.coalesce(self.data['title'], u'')) 542 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
543 #----------------------------------------------------------------
545 pass
546 547 #------------------------------------------------------------ 548 from Gnumed.wxGladeWidgets import wxgNameGenderDOBEditAreaPnl 549
550 -class cNameGenderDOBEditAreaPnl(wxgNameGenderDOBEditAreaPnl.wxgNameGenderDOBEditAreaPnl):
551 """An edit area for editing/creating name/gender/dob. 552 553 Does NOT act on/listen to the current patient. 554 """
555 - def __init__(self, *args, **kwargs):
556 557 self.__name = kwargs['name'] 558 del kwargs['name'] 559 self.__identity = gmPerson.cIdentity(aPK_obj = self.__name['pk_identity']) 560 561 wxgNameGenderDOBEditAreaPnl.wxgNameGenderDOBEditAreaPnl.__init__(self, *args, **kwargs) 562 563 self.__register_interests() 564 self.refresh()
565 #-------------------------------------------------------- 566 # external API 567 #--------------------------------------------------------
568 - def refresh(self):
569 if self.__name is None: 570 return 571 572 self._PRW_title.SetText(gmTools.coalesce(self.__name['title'], u'')) 573 self._PRW_firstname.SetText(self.__name['firstnames']) 574 self._PRW_lastname.SetText(self.__name['lastnames']) 575 self._PRW_nick.SetText(gmTools.coalesce(self.__name['preferred'], u'')) 576 self._PRW_dob.SetText ( 577 value = self.__identity.get_formatted_dob(format = '%Y-%m-%d %H:%M', encoding = gmI18N.get_encoding()), 578 data = self.__identity['dob'] 579 ) 580 self._PRW_gender.SetData(self.__name['gender']) 581 self._CHBOX_active.SetValue(self.__name['active_name']) 582 self._DP_dod.SetValue(self.__identity['deceased']) 583 self._TCTRL_comment.SetValue(gmTools.coalesce(self.__name['comment'], u''))
584 # FIXME: clear fields 585 # else: 586 # pass 587 #--------------------------------------------------------
588 - def save(self):
589 590 if not self.__valid_for_save(): 591 return False 592 593 self.__identity['gender'] = self._PRW_gender.GetData() 594 if self._PRW_dob.GetValue().strip() == u'': 595 self.__identity['dob'] = None 596 else: 597 self.__identity['dob'] = self._PRW_dob.GetData().get_pydt() 598 self.__identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'') 599 self.__identity['deceased'] = self._DP_dod.GetValue(as_pydt = True) 600 self.__identity.save_payload() 601 602 active = self._CHBOX_active.GetValue() 603 first = self._PRW_firstname.GetValue().strip() 604 last = self._PRW_lastname.GetValue().strip() 605 old_nick = self.__name['preferred'] 606 607 # is it a new name ? 608 old_name = self.__name['firstnames'] + self.__name['lastnames'] 609 if (first + last) != old_name: 610 self.__name = self.__identity.add_name(first, last, active) 611 612 self.__name['active_name'] = active 613 self.__name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'') 614 self.__name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 615 616 self.__name.save_payload() 617 618 return True
619 #-------------------------------------------------------- 620 # event handling 621 #--------------------------------------------------------
622 - def __register_interests(self):
623 self._PRW_firstname.add_callback_on_lose_focus(self._on_name_set)
624 #--------------------------------------------------------
625 - def _on_name_set(self):
626 """Set the gender according to entered firstname. 627 628 Matches are fetched from existing records in backend. 629 """ 630 firstname = self._PRW_firstname.GetValue().strip() 631 if firstname == u'': 632 return True 633 rows, idx = gmPG2.run_ro_queries(queries = [{ 634 'cmd': u"select gender from dem.name_gender_map where name ilike %s", 635 'args': [firstname] 636 }]) 637 if len(rows) == 0: 638 return True 639 wx.CallAfter(self._PRW_gender.SetData, rows[0][0]) 640 return True
641 #-------------------------------------------------------- 642 # internal helpers 643 #--------------------------------------------------------
644 - def __valid_for_save(self):
645 646 has_error = False 647 648 if self._PRW_gender.GetData() is None: 649 self._PRW_gender.SetBackgroundColour('pink') 650 self._PRW_gender.Refresh() 651 self._PRW_gender.SetFocus() 652 has_error = True 653 else: 654 self._PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 655 self._PRW_gender.Refresh() 656 657 if not self._PRW_dob.is_valid_timestamp(): 658 val = self._PRW_dob.GetValue().strip() 659 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val) 660 self._PRW_dob.SetBackgroundColour('pink') 661 self._PRW_dob.Refresh() 662 self._PRW_dob.SetFocus() 663 has_error = True 664 else: 665 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 666 self._PRW_dob.Refresh() 667 668 if not self._DP_dod.is_valid_timestamp(): 669 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.')) 670 self._DP_dod.SetBackgroundColour('pink') 671 self._DP_dod.Refresh() 672 self._DP_dod.SetFocus() 673 has_error = True 674 else: 675 self._DP_dod.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 676 self._DP_dod.Refresh() 677 678 if self._PRW_lastname.GetValue().strip() == u'': 679 self._PRW_lastname.SetBackgroundColour('pink') 680 self._PRW_lastname.Refresh() 681 self._PRW_lastname.SetFocus() 682 has_error = True 683 else: 684 self._PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 685 self._PRW_lastname.Refresh() 686 687 if self._PRW_firstname.GetValue().strip() == u'': 688 self._PRW_firstname.SetBackgroundColour('pink') 689 self._PRW_firstname.Refresh() 690 self._PRW_firstname.SetFocus() 691 has_error = True 692 else: 693 self._PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 694 self._PRW_firstname.Refresh() 695 696 return (has_error is False)
697 #------------------------------------------------------------ 698 # list manager 699 #------------------------------------------------------------
700 -class cPersonNamesManagerPnl(gmListWidgets.cGenericListManagerPnl):
701 """A list for managing a person's names. 702 703 Does NOT act on/listen to the current patient. 704 """
705 - def __init__(self, *args, **kwargs):
706 707 try: 708 self.__identity = kwargs['identity'] 709 del kwargs['identity'] 710 except KeyError: 711 self.__identity = None 712 713 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 714 715 self.new_callback = self._add_name 716 self.edit_callback = self._edit_name 717 self.delete_callback = self._del_name 718 self.refresh_callback = self.refresh 719 720 self.__init_ui() 721 self.refresh()
722 #-------------------------------------------------------- 723 # external API 724 #--------------------------------------------------------
725 - def refresh(self, *args, **kwargs):
726 if self.__identity is None: 727 self._LCTRL_items.set_string_items() 728 return 729 730 names = self.__identity.get_names() 731 self._LCTRL_items.set_string_items ( 732 items = [ [ 733 gmTools.bool2str(n['active_name'], 'X', ''), 734 n['lastnames'], 735 n['firstnames'], 736 gmTools.coalesce(n['preferred'], u''), 737 gmTools.coalesce(n['comment'], u'') 738 ] for n in names ] 739 ) 740 self._LCTRL_items.set_column_widths() 741 self._LCTRL_items.set_data(data = names)
742 #-------------------------------------------------------- 743 # internal helpers 744 #--------------------------------------------------------
745 - def __init_ui(self):
746 self._LCTRL_items.set_columns(columns = [ 747 _('Active'), 748 _('Lastname'), 749 _('Firstname(s)'), 750 _('Preferred Name'), 751 _('Comment') 752 ])
753 #--------------------------------------------------------
754 - def _add_name(self):
755 ea = cNameGenderDOBEditAreaPnl(self, -1, name = self.__identity.get_active_name()) 756 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 757 dlg.SetTitle(_('Adding new name')) 758 if dlg.ShowModal() == wx.ID_OK: 759 dlg.Destroy() 760 return True 761 dlg.Destroy() 762 return False
763 #--------------------------------------------------------
764 - def _edit_name(self, name):
765 ea = cNameGenderDOBEditAreaPnl(self, -1, name = name) 766 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 767 dlg.SetTitle(_('Editing name')) 768 if dlg.ShowModal() == wx.ID_OK: 769 dlg.Destroy() 770 return True 771 dlg.Destroy() 772 return False
773 #--------------------------------------------------------
774 - def _del_name(self, name):
775 776 if len(self.__identity.get_names()) == 1: 777 gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the only name of a person.'), beep = True) 778 return False 779 780 go_ahead = gmGuiHelpers.gm_show_question ( 781 _( 'It is often advisable to keep old names around and\n' 782 'just create a new "currently active" name.\n' 783 '\n' 784 'This allows finding the patient by both the old\n' 785 'and the new name (think before/after marriage).\n' 786 '\n' 787 'Do you still want to really delete\n' 788 "this name from the patient ?" 789 ), 790 _('Deleting name') 791 ) 792 if not go_ahead: 793 return False 794 795 self.__identity.delete_name(name = name) 796 return True
797 #-------------------------------------------------------- 798 # properties 799 #--------------------------------------------------------
800 - def _get_identity(self):
801 return self.__identity
802
803 - def _set_identity(self, identity):
804 self.__identity = identity 805 self.refresh()
806 807 identity = property(_get_identity, _set_identity)
808 #------------------------------------------------------------
809 -class cPersonIDsManagerPnl(gmListWidgets.cGenericListManagerPnl):
810 """A list for managing a person's external IDs. 811 812 Does NOT act on/listen to the current patient. 813 """
814 - def __init__(self, *args, **kwargs):
815 816 try: 817 self.__identity = kwargs['identity'] 818 del kwargs['identity'] 819 except KeyError: 820 self.__identity = None 821 822 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 823 824 self.new_callback = self._add_id 825 self.edit_callback = self._edit_id 826 self.delete_callback = self._del_id 827 self.refresh_callback = self.refresh 828 829 self.__init_ui() 830 self.refresh()
831 #-------------------------------------------------------- 832 # external API 833 #--------------------------------------------------------
834 - def refresh(self, *args, **kwargs):
835 if self.__identity is None: 836 self._LCTRL_items.set_string_items() 837 return 838 839 ids = self.__identity.get_external_ids() 840 self._LCTRL_items.set_string_items ( 841 items = [ [ 842 i['name'], 843 i['value'], 844 gmTools.coalesce(i['issuer'], u''), 845 i['context'], 846 gmTools.coalesce(i['comment'], u'') 847 ] for i in ids 848 ] 849 ) 850 self._LCTRL_items.set_column_widths() 851 self._LCTRL_items.set_data(data = ids)
852 #-------------------------------------------------------- 853 # internal helpers 854 #--------------------------------------------------------
855 - def __init_ui(self):
856 self._LCTRL_items.set_columns(columns = [ 857 _('ID type'), 858 _('Value'), 859 _('Issuer'), 860 _('Context'), 861 _('Comment') 862 ])
863 #--------------------------------------------------------
864 - def _add_id(self):
865 ea = cExternalIDEditAreaPnl(self, -1) 866 ea.identity = self.__identity 867 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 868 dlg.SetTitle(_('Adding new external ID')) 869 if dlg.ShowModal() == wx.ID_OK: 870 dlg.Destroy() 871 return True 872 dlg.Destroy() 873 return False
874 #--------------------------------------------------------
875 - def _edit_id(self, ext_id):
876 ea = cExternalIDEditAreaPnl(self, -1, external_id = ext_id) 877 ea.identity = self.__identity 878 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 879 dlg.SetTitle(_('Editing external ID')) 880 if dlg.ShowModal() == wx.ID_OK: 881 dlg.Destroy() 882 return True 883 dlg.Destroy() 884 return False
885 #--------------------------------------------------------
886 - def _del_id(self, ext_id):
887 go_ahead = gmGuiHelpers.gm_show_question ( 888 _( 'Do you really want to delete this\n' 889 'external ID from the patient ?'), 890 _('Deleting external ID') 891 ) 892 if not go_ahead: 893 return False 894 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id']) 895 return True
896 #-------------------------------------------------------- 897 # properties 898 #--------------------------------------------------------
899 - def _get_identity(self):
900 return self.__identity
901
902 - def _set_identity(self, identity):
903 self.__identity = identity 904 self.refresh()
905 906 identity = property(_get_identity, _set_identity)
907 #------------------------------------------------------------ 908 # integrated panels 909 #------------------------------------------------------------ 910 from Gnumed.wxGladeWidgets import wxgPersonIdentityManagerPnl 911
912 -class cPersonIdentityManagerPnl(wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl):
913 """A panel for editing identity data for a person. 914 915 - provides access to: 916 - name 917 - external IDs 918 919 Does NOT act on/listen to the current patient. 920 """
921 - def __init__(self, *args, **kwargs):
922 923 wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl.__init__(self, *args, **kwargs) 924 925 self.__identity = None 926 self.refresh()
927 #-------------------------------------------------------- 928 # external API 929 #--------------------------------------------------------
930 - def refresh(self):
931 self._PNL_names.identity = self.__identity 932 self._PNL_ids.identity = self.__identity 933 # this is an Edit Area: 934 self._PNL_identity.mode = 'new' 935 self._PNL_identity.data = self.__identity 936 if self.__identity is not None: 937 self._PNL_identity.mode = 'edit'
938 #-------------------------------------------------------- 939 # properties 940 #--------------------------------------------------------
941 - def _get_identity(self):
942 return self.__identity
943
944 - def _set_identity(self, identity):
945 self.__identity = identity 946 self.refresh()
947 948 identity = property(_get_identity, _set_identity) 949 #-------------------------------------------------------- 950 # event handlers 951 #--------------------------------------------------------
953 if not self._PNL_identity.save(): 954 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save identity. Incomplete information.'), beep = True)
955 #--------------------------------------------------------
956 - def _on_reload_identity_button_pressed(self, event):
957 self._PNL_identity.refresh()
958 959 #============================================================ 960 from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl 961
962 -class cPersonSocialNetworkManagerPnl(wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl):
963 - def __init__(self, *args, **kwargs):
964 965 wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl.__init__(self, *args, **kwargs) 966 967 self.__identity = None 968 self.refresh()
969 #-------------------------------------------------------- 970 # external API 971 #--------------------------------------------------------
972 - def refresh(self):
973 974 tt = _("Link another person in this database as the emergency contact.") 975 976 if self.__identity is None: 977 self._TCTRL_er_contact.SetValue(u'') 978 self._TCTRL_person.person = None 979 self._TCTRL_person.SetToolTipString(tt) 980 return 981 982 self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], u'')) 983 if self.__identity['pk_emergency_contact'] is not None: 984 ident = gmPerson.cIdentity(aPK_obj = self.__identity['pk_emergency_contact']) 985 self._TCTRL_person.person = ident 986 tt = u'%s\n\n%s\n\n%s' % ( 987 tt, 988 ident['description_gender'], 989 u'\n'.join([ 990 u'%s: %s%s' % ( 991 c['l10n_comm_type'], 992 c['url'], 993 gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), u'', u'') 994 ) 995 for c in ident.get_comm_channels() 996 ]) 997 ) 998 else: 999 self._TCTRL_person.person = None 1000 1001 self._TCTRL_person.SetToolTipString(tt)
1002 #-------------------------------------------------------- 1003 # properties 1004 #--------------------------------------------------------
1005 - def _get_identity(self):
1006 return self.__identity
1007
1008 - def _set_identity(self, identity):
1009 self.__identity = identity 1010 self.refresh()
1011 1012 identity = property(_get_identity, _set_identity) 1013 #-------------------------------------------------------- 1014 # event handlers 1015 #--------------------------------------------------------
1016 - def _on_save_button_pressed(self, event):
1017 if self.__identity is not None: 1018 self.__identity['emergency_contact'] = self._TCTRL_er_contact.GetValue().strip() 1019 if self._TCTRL_person.person is not None: 1020 self.__identity['pk_emergency_contact'] = self._TCTRL_person.person.ID 1021 self.__identity.save() 1022 1023 event.Skip()
1024 #--------------------------------------------------------
1025 - def _on_remove_contact_button_pressed(self, event):
1026 event.Skip() 1027 1028 if self.__identity is None: 1029 return 1030 1031 self._TCTRL_person.person = None 1032 1033 self.__identity['pk_emergency_contact'] = None 1034 self.__identity.save()
1035 #--------------------------------------------------------
1036 - def _on_button_activate_contact_pressed(self, event):
1037 ident = self._TCTRL_person.person 1038 if ident is not None: 1039 from Gnumed.wxpython import gmPatSearchWidgets 1040 gmPatSearchWidgets.set_active_patient(patient = ident, forced_reload = False) 1041 1042 event.Skip()
1043 #============================================================ 1044 # new-patient widgets 1045 #============================================================
1046 -def create_new_person(parent=None, activate=False):
1047 1048 dbcfg = gmCfg.cCfgSQL() 1049 1050 def_region = dbcfg.get2 ( 1051 option = u'person.create.default_region', 1052 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1053 bias = u'user' 1054 ) 1055 def_country = None 1056 1057 if def_region is None: 1058 def_country = dbcfg.get2 ( 1059 option = u'person.create.default_country', 1060 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1061 bias = u'user' 1062 ) 1063 else: 1064 countries = gmDemographicRecord.get_country_for_region(region = def_region) 1065 if len(countries) == 1: 1066 def_country = countries[0]['l10n_country'] 1067 1068 if parent is None: 1069 parent = wx.GetApp().GetTopWindow() 1070 1071 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region) 1072 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) 1073 dlg.SetTitle(_('Adding new person')) 1074 ea._PRW_lastname.SetFocus() 1075 result = dlg.ShowModal() 1076 pat = ea.data 1077 dlg.Destroy() 1078 1079 if result != wx.ID_OK: 1080 return False 1081 1082 _log.debug('created new person [%s]', pat.ID) 1083 1084 if activate: 1085 from Gnumed.wxpython import gmPatSearchWidgets 1086 gmPatSearchWidgets.set_active_patient(patient = pat) 1087 1088 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin') 1089 1090 return True
1091 #============================================================ 1092 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl 1093
1094 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
1095
1096 - def __init__(self, *args, **kwargs):
1097 1098 try: 1099 self.default_region = kwargs['region'] 1100 del kwargs['region'] 1101 except KeyError: 1102 self.default_region = None 1103 1104 try: 1105 self.default_country = kwargs['country'] 1106 del kwargs['country'] 1107 except KeyError: 1108 self.default_country = None 1109 1110 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs) 1111 gmEditArea.cGenericEditAreaMixin.__init__(self) 1112 1113 self.mode = 'new' 1114 self.data = None 1115 self._address = None 1116 1117 self.__init_ui() 1118 self.__register_interests()
1119 #---------------------------------------------------------------- 1120 # internal helpers 1121 #----------------------------------------------------------------
1122 - def __init_ui(self):
1123 self._PRW_lastname.final_regex = '.+' 1124 self._PRW_firstnames.final_regex = '.+' 1125 self._PRW_address_searcher.selection_only = False 1126 1127 # don't do that or else it will turn <invalid> into <today> :-( 1128 # low = wx.DateTimeFromDMY(1,0,1900) 1129 # hi = wx.DateTime() 1130 # self._DP_dob.SetRange(low, hi.SetToCurrent()) 1131 #self._DP_dob.SetValue(None) 1132 1133 # only if we would support None on selection_only's: 1134 # self._PRW_external_id_type.selection_only = True 1135 1136 if self.default_country is not None: 1137 self._PRW_country.SetText(value = self.default_country) 1138 1139 if self.default_region is not None: 1140 self._PRW_region.SetText(value = self.default_region)
1141 #----------------------------------------------------------------
1142 - def __perhaps_invalidate_address_searcher(self, ctrl=None, field=None):
1143 1144 adr = self._PRW_address_searcher.get_address() 1145 if adr is None: 1146 return True 1147 1148 if ctrl.GetValue().strip() != adr[field]: 1149 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None) 1150 return True 1151 1152 return False
1153 #----------------------------------------------------------------
1155 adr = self._PRW_address_searcher.get_address() 1156 if adr is None: 1157 return True 1158 1159 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode']) 1160 1161 self._PRW_street.SetText(value = adr['street'], data = adr['street']) 1162 self._PRW_street.set_context(context = u'zip', val = adr['postcode']) 1163 1164 self._TCTRL_number.SetValue(adr['number']) 1165 1166 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb']) 1167 self._PRW_urb.set_context(context = u'zip', val = adr['postcode']) 1168 1169 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state']) 1170 self._PRW_region.set_context(context = u'zip', val = adr['postcode']) 1171 1172 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country']) 1173 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
1174 #----------------------------------------------------------------
1175 - def __identity_valid_for_save(self):
1176 error = False 1177 1178 # name fields 1179 if self._PRW_lastname.GetValue().strip() == u'': 1180 error = True 1181 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.')) 1182 self._PRW_lastname.display_as_valid(False) 1183 else: 1184 self._PRW_lastname.display_as_valid(True) 1185 1186 if self._PRW_firstnames.GetValue().strip() == '': 1187 error = True 1188 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.')) 1189 self._PRW_firstnames.display_as_valid(False) 1190 else: 1191 self._PRW_firstnames.display_as_valid(True) 1192 1193 # gender 1194 if self._PRW_gender.GetData() is None: 1195 error = True 1196 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.')) 1197 self._PRW_gender.display_as_valid(False) 1198 else: 1199 self._PRW_gender.display_as_valid(True) 1200 1201 # dob validation 1202 dob = self._DP_dob.GetValue(as_pydt = False, invalid_as_none = True) 1203 # 1) valid timestamp ? 1204 if self._DP_dob.is_valid_timestamp(allow_none = False): # properly colors the field 1205 # but year also usable ? 1206 msg = None 1207 if (dob.GetYear() < 1900): 1208 msg = _( 1209 'DOB: %s\n' 1210 '\n' 1211 'While this is a valid point in time Python does\n' 1212 'not know how to deal with it.\n' 1213 '\n' 1214 'We suggest using January 1st 1901 instead and adding\n' 1215 'the true date of birth to the patient comment.\n' 1216 '\n' 1217 'Sorry for the inconvenience %s' 1218 ) % (dob, gmTools.u_frowning_face) 1219 elif dob > gmDateTime.wx_now_here(wx = wx): 1220 msg = _( 1221 'DOB: %s\n' 1222 '\n' 1223 'Date of birth in the future !' 1224 ) % dob 1225 1226 if msg is not None: 1227 error = True 1228 gmGuiHelpers.gm_show_error ( 1229 msg, 1230 _('Registering new person') 1231 ) 1232 self._DP_dob.display_as_valid(False) 1233 self._DP_dob.SetFocus() 1234 # 2) invalid timestamp ? 1235 # Do we have to check for u'', ever ? 1236 else: 1237 allow_empty_dob = gmGuiHelpers.gm_show_question ( 1238 _( 1239 'Are you sure you want to register this person\n' 1240 'without a valid date of birth ?\n' 1241 '\n' 1242 'This can be useful for temporary staff members\n' 1243 'but will provoke nag screens if this person\n' 1244 'becomes a patient.\n' 1245 ), 1246 _('Registering new person') 1247 ) 1248 if allow_empty_dob: 1249 self._DP_dob.display_as_valid(True) 1250 else: 1251 error = True 1252 self._DP_dob.SetFocus() 1253 1254 # TOB validation if non-empty 1255 # if self._TCTRL_tob.GetValue().strip() != u'': 1256 1257 return (not error)
1258 #----------------------------------------------------------------
1259 - def __address_valid_for_save(self, empty_address_is_valid=False):
1260 1261 # existing address ? if so set other fields 1262 if self._PRW_address_searcher.GetData() is not None: 1263 wx.CallAfter(self.__set_fields_from_address_searcher) 1264 return True 1265 1266 # must either all contain something or none of them 1267 fields_to_fill = ( 1268 self._TCTRL_number, 1269 self._PRW_zip, 1270 self._PRW_street, 1271 self._PRW_urb, 1272 self._PRW_region, 1273 self._PRW_country 1274 ) 1275 no_of_filled_fields = 0 1276 1277 for field in fields_to_fill: 1278 if field.GetValue().strip() != u'': 1279 no_of_filled_fields += 1 1280 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 1281 field.Refresh() 1282 1283 # empty address ? 1284 if no_of_filled_fields == 0: 1285 if empty_address_is_valid: 1286 return True 1287 else: 1288 return None 1289 1290 # incompletely filled address ? 1291 if no_of_filled_fields != len(fields_to_fill): 1292 for field in fields_to_fill: 1293 if field.GetValue().strip() == u'': 1294 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 1295 field.SetFocus() 1296 field.Refresh() 1297 msg = _('To properly create an address, all the related fields must be filled in.') 1298 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 1299 return False 1300 1301 # fields which must contain a selected item 1302 # FIXME: they must also contain an *acceptable combination* which 1303 # FIXME: can only be tested against the database itself ... 1304 strict_fields = ( 1305 self._PRW_region, 1306 self._PRW_country 1307 ) 1308 error = False 1309 for field in strict_fields: 1310 if field.GetData() is None: 1311 error = True 1312 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 1313 field.SetFocus() 1314 else: 1315 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 1316 field.Refresh() 1317 1318 if error: 1319 msg = _('This field must contain an item selected from the dropdown list.') 1320 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 1321 return False 1322 1323 return True
1324 #----------------------------------------------------------------
1325 - def __register_interests(self):
1326 1327 # identity 1328 self._PRW_firstnames.add_callback_on_lose_focus(self._on_leaving_firstname) 1329 1330 # address 1331 self._PRW_address_searcher.add_callback_on_lose_focus(self._on_leaving_adress_searcher) 1332 1333 # invalidate address searcher when any field edited 1334 self._PRW_street.add_callback_on_lose_focus(self._invalidate_address_searcher) 1335 wx.EVT_KILL_FOCUS(self._TCTRL_number, self._invalidate_address_searcher) 1336 self._PRW_urb.add_callback_on_lose_focus(self._invalidate_address_searcher) 1337 self._PRW_region.add_callback_on_lose_focus(self._invalidate_address_searcher) 1338 1339 self._PRW_zip.add_callback_on_lose_focus(self._on_leaving_zip) 1340 self._PRW_country.add_callback_on_lose_focus(self._on_leaving_country)
1341 #---------------------------------------------------------------- 1342 # event handlers 1343 #----------------------------------------------------------------
1344 - def _on_leaving_firstname(self):
1345 """Set the gender according to entered firstname. 1346 1347 Matches are fetched from existing records in backend. 1348 """ 1349 # only set if not already set so as to not 1350 # overwrite a change by the user 1351 if self._PRW_gender.GetData() is not None: 1352 return True 1353 1354 firstname = self._PRW_firstnames.GetValue().strip() 1355 if firstname == u'': 1356 return True 1357 1358 gender = gmPerson.map_firstnames2gender(firstnames = firstname) 1359 if gender is None: 1360 return True 1361 1362 wx.CallAfter(self._PRW_gender.SetData, gender) 1363 return True
1364 #----------------------------------------------------------------
1365 - def _on_leaving_zip(self):
1366 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode') 1367 1368 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'') 1369 self._PRW_street.set_context(context = u'zip', val = zip_code) 1370 self._PRW_urb.set_context(context = u'zip', val = zip_code) 1371 self._PRW_region.set_context(context = u'zip', val = zip_code) 1372 self._PRW_country.set_context(context = u'zip', val = zip_code) 1373 1374 return True
1375 #----------------------------------------------------------------
1376 - def _on_leaving_country(self):
1377 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country') 1378 1379 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'') 1380 self._PRW_region.set_context(context = u'country', val = country) 1381 1382 return True
1383 #----------------------------------------------------------------
1384 - def _invalidate_address_searcher(self, *args, **kwargs):
1385 mapping = [ 1386 (self._PRW_street, 'street'), 1387 (self._TCTRL_number, 'number'), 1388 (self._PRW_urb, 'urb'), 1389 (self._PRW_region, 'l10n_state') 1390 ] 1391 1392 # loop through fields and invalidate address searcher if different 1393 for ctrl, field in mapping: 1394 if self.__perhaps_invalidate_address_searcher(ctrl, field): 1395 return True 1396 1397 return True
1398 #----------------------------------------------------------------
1400 adr = self._PRW_address_searcher.get_address() 1401 if adr is None: 1402 return True 1403 1404 wx.CallAfter(self.__set_fields_from_address_searcher) 1405 return True
1406 #---------------------------------------------------------------- 1407 # generic Edit Area mixin API 1408 #----------------------------------------------------------------
1409 - def _valid_for_save(self):
1410 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
1411 #----------------------------------------------------------------
1412 - def _save_as_new(self):
1413 1414 # identity 1415 new_identity = gmPerson.create_identity ( 1416 gender = self._PRW_gender.GetData(), 1417 dob = self._DP_dob.get_pydt(), 1418 lastnames = self._PRW_lastname.GetValue().strip(), 1419 firstnames = self._PRW_firstnames.GetValue().strip() 1420 ) 1421 _log.debug('identity created: %s' % new_identity) 1422 1423 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip()) 1424 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u'')) 1425 #TOB 1426 new_identity.save() 1427 1428 name = new_identity.get_active_name() 1429 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 1430 name.save() 1431 1432 # address 1433 is_valid = self.__address_valid_for_save(empty_address_is_valid = False) 1434 if is_valid is True: 1435 # because we currently only check for non-emptiness 1436 # we must still deal with database errors 1437 try: 1438 new_identity.link_address ( 1439 number = self._TCTRL_number.GetValue().strip(), 1440 street = self._PRW_street.GetValue().strip(), 1441 postcode = self._PRW_zip.GetValue().strip(), 1442 urb = self._PRW_urb.GetValue().strip(), 1443 state = self._PRW_region.GetData(), 1444 country = self._PRW_country.GetData() 1445 ) 1446 except gmPG2.dbapi.InternalError: 1447 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip()) 1448 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip()) 1449 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip()) 1450 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip()) 1451 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip()) 1452 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip()) 1453 _log.exception('cannot link address') 1454 gmGuiHelpers.gm_show_error ( 1455 aTitle = _('Saving address'), 1456 aMessage = _( 1457 'Cannot save this address.\n' 1458 '\n' 1459 'You will have to add it via the Demographics plugin.\n' 1460 ) 1461 ) 1462 elif is_valid is False: 1463 gmGuiHelpers.gm_show_error ( 1464 aTitle = _('Saving address'), 1465 aMessage = _( 1466 'Address not saved.\n' 1467 '\n' 1468 'You will have to add it via the Demographics plugin.\n' 1469 ) 1470 ) 1471 # else it is None which means empty address which we ignore 1472 1473 # phone 1474 new_identity.link_comm_channel ( 1475 comm_medium = u'homephone', 1476 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''), 1477 is_confidential = False 1478 ) 1479 1480 # external ID 1481 pk_type = self._PRW_external_id_type.GetData() 1482 id_value = self._TCTRL_external_id_value.GetValue().strip() 1483 if (pk_type is not None) and (id_value != u''): 1484 new_identity.add_external_id(value = id_value, pk_type = pk_type) 1485 1486 # occupation 1487 new_identity.link_occupation ( 1488 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'') 1489 ) 1490 1491 self.data = new_identity 1492 return True
1493 #----------------------------------------------------------------
1494 - def _save_as_update(self):
1495 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
1496 #----------------------------------------------------------------
1497 - def _refresh_as_new(self):
1498 # FIXME: button "empty out" 1499 return
1500 #----------------------------------------------------------------
1501 - def _refresh_from_existing(self):
1502 return # there is no forward button so nothing to do here
1503 #----------------------------------------------------------------
1505 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
1506 #============================================================ 1507 # new-patient wizard classes 1508 #============================================================
1509 -class cBasicPatDetailsPage(wx.wizard.WizardPageSimple):
1510 """ 1511 Wizard page for entering patient's basic demographic information 1512 """ 1513 1514 form_fields = ( 1515 'firstnames', 'lastnames', 'nick', 'dob', 'gender', 'title', 'occupation', 1516 'address_number', 'zip_code', 'street', 'town', 'state', 'country', 'phone', 'comment' 1517 ) 1518
1519 - def __init__(self, parent, title):
1520 """ 1521 Creates a new instance of BasicPatDetailsPage 1522 @param parent - The parent widget 1523 @type parent - A wx.Window instance 1524 @param tile - The title of the page 1525 @type title - A StringType instance 1526 """ 1527 wx.wizard.WizardPageSimple.__init__(self, parent) #, bitmap = gmGuiHelpers.gm_icon(_('oneperson')) 1528 self.__title = title 1529 self.__do_layout() 1530 self.__register_interests()
1531 #--------------------------------------------------------
1532 - def __do_layout(self):
1533 PNL_form = wx.Panel(self, -1) 1534 1535 # last name 1536 STT_lastname = wx.StaticText(PNL_form, -1, _('Last name')) 1537 STT_lastname.SetForegroundColour('red') 1538 self.PRW_lastname = cLastnamePhraseWheel(parent = PNL_form, id = -1) 1539 self.PRW_lastname.SetToolTipString(_('Required: lastname (family name)')) 1540 1541 # first name 1542 STT_firstname = wx.StaticText(PNL_form, -1, _('First name(s)')) 1543 STT_firstname.SetForegroundColour('red') 1544 self.PRW_firstname = cFirstnamePhraseWheel(parent = PNL_form, id = -1) 1545 self.PRW_firstname.SetToolTipString(_('Required: surname/given name/first name')) 1546 1547 # nickname 1548 STT_nick = wx.StaticText(PNL_form, -1, _('Nick name')) 1549 self.PRW_nick = cNicknamePhraseWheel(parent = PNL_form, id = -1) 1550 1551 # DOB 1552 STT_dob = wx.StaticText(PNL_form, -1, _('Date of birth')) 1553 STT_dob.SetForegroundColour('red') 1554 self.PRW_dob = gmDateTimeInput.cFuzzyTimestampInput(parent = PNL_form, id = -1) 1555 self.PRW_dob.SetToolTipString(_("Required: date of birth, if unknown or aliasing wanted then invent one")) 1556 1557 # gender 1558 STT_gender = wx.StaticText(PNL_form, -1, _('Gender')) 1559 STT_gender.SetForegroundColour('red') 1560 self.PRW_gender = cGenderSelectionPhraseWheel(parent = PNL_form, id=-1) 1561 self.PRW_gender.SetToolTipString(_("Required: gender of patient")) 1562 1563 # title 1564 STT_title = wx.StaticText(PNL_form, -1, _('Title')) 1565 self.PRW_title = cTitlePhraseWheel(parent = PNL_form, id = -1) 1566 1567 # zip code 1568 STT_zip_code = wx.StaticText(PNL_form, -1, _('Postal code')) 1569 STT_zip_code.SetForegroundColour('orange') 1570 self.PRW_zip_code = gmPersonContactWidgets.cZipcodePhraseWheel(parent = PNL_form, id = -1) 1571 self.PRW_zip_code.SetToolTipString(_("primary/home address: zip/postal code")) 1572 1573 # street 1574 STT_street = wx.StaticText(PNL_form, -1, _('Street')) 1575 STT_street.SetForegroundColour('orange') 1576 self.PRW_street = gmPersonContactWidgets.cStreetPhraseWheel(parent = PNL_form, id = -1) 1577 self.PRW_street.SetToolTipString(_("primary/home address: name of street")) 1578 1579 # address number 1580 STT_address_number = wx.StaticText(PNL_form, -1, _('Number')) 1581 STT_address_number.SetForegroundColour('orange') 1582 self.TTC_address_number = wx.TextCtrl(PNL_form, -1) 1583 self.TTC_address_number.SetToolTipString(_("primary/home address: address number")) 1584 1585 # town 1586 STT_town = wx.StaticText(PNL_form, -1, _('Place')) 1587 STT_town.SetForegroundColour('orange') 1588 self.PRW_town = gmPersonContactWidgets.cUrbPhraseWheel(parent = PNL_form, id = -1) 1589 self.PRW_town.SetToolTipString(_("primary/home address: city/town/village/dwelling/...")) 1590 1591 # state 1592 STT_state = wx.StaticText(PNL_form, -1, _('Region')) 1593 STT_state.SetForegroundColour('orange') 1594 self.PRW_state = gmPersonContactWidgets.cStateSelectionPhraseWheel(parent=PNL_form, id=-1) 1595 self.PRW_state.SetToolTipString(_("primary/home address: state/province/county/...")) 1596 1597 # country 1598 STT_country = wx.StaticText(PNL_form, -1, _('Country')) 1599 STT_country.SetForegroundColour('orange') 1600 self.PRW_country = gmPersonContactWidgets.cCountryPhraseWheel(parent = PNL_form, id = -1) 1601 self.PRW_country.SetToolTipString(_("primary/home address: country")) 1602 1603 # phone 1604 STT_phone = wx.StaticText(PNL_form, -1, _('Phone')) 1605 self.TTC_phone = wx.TextCtrl(PNL_form, -1) 1606 self.TTC_phone.SetToolTipString(_("phone number at home")) 1607 1608 # occupation 1609 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation')) 1610 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1) 1611 1612 # comment 1613 STT_comment = wx.StaticText(PNL_form, -1, _('Comment')) 1614 self.TCTRL_comment = wx.TextCtrl(PNL_form, -1) 1615 self.TCTRL_comment.SetToolTipString(_('A comment on this patient.')) 1616 1617 # form main validator 1618 self.form_DTD = cFormDTD(fields = self.__class__.form_fields) 1619 PNL_form.SetValidator(cBasicPatDetailsPageValidator(dtd = self.form_DTD)) 1620 1621 # layout input widgets 1622 SZR_input = wx.FlexGridSizer(cols = 2, rows = 16, vgap = 4, hgap = 4) 1623 SZR_input.AddGrowableCol(1) 1624 SZR_input.Add(STT_lastname, 0, wx.SHAPED) 1625 SZR_input.Add(self.PRW_lastname, 1, wx.EXPAND) 1626 SZR_input.Add(STT_firstname, 0, wx.SHAPED) 1627 SZR_input.Add(self.PRW_firstname, 1, wx.EXPAND) 1628 SZR_input.Add(STT_nick, 0, wx.SHAPED) 1629 SZR_input.Add(self.PRW_nick, 1, wx.EXPAND) 1630 SZR_input.Add(STT_dob, 0, wx.SHAPED) 1631 SZR_input.Add(self.PRW_dob, 1, wx.EXPAND) 1632 SZR_input.Add(STT_gender, 0, wx.SHAPED) 1633 SZR_input.Add(self.PRW_gender, 1, wx.EXPAND) 1634 SZR_input.Add(STT_title, 0, wx.SHAPED) 1635 SZR_input.Add(self.PRW_title, 1, wx.EXPAND) 1636 SZR_input.Add(STT_zip_code, 0, wx.SHAPED) 1637 SZR_input.Add(self.PRW_zip_code, 1, wx.EXPAND) 1638 SZR_input.Add(STT_street, 0, wx.SHAPED) 1639 SZR_input.Add(self.PRW_street, 1, wx.EXPAND) 1640 SZR_input.Add(STT_address_number, 0, wx.SHAPED) 1641 SZR_input.Add(self.TTC_address_number, 1, wx.EXPAND) 1642 SZR_input.Add(STT_town, 0, wx.SHAPED) 1643 SZR_input.Add(self.PRW_town, 1, wx.EXPAND) 1644 SZR_input.Add(STT_state, 0, wx.SHAPED) 1645 SZR_input.Add(self.PRW_state, 1, wx.EXPAND) 1646 SZR_input.Add(STT_country, 0, wx.SHAPED) 1647 SZR_input.Add(self.PRW_country, 1, wx.EXPAND) 1648 SZR_input.Add(STT_phone, 0, wx.SHAPED) 1649 SZR_input.Add(self.TTC_phone, 1, wx.EXPAND) 1650 SZR_input.Add(STT_occupation, 0, wx.SHAPED) 1651 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND) 1652 SZR_input.Add(STT_comment, 0, wx.SHAPED) 1653 SZR_input.Add(self.TCTRL_comment, 1, wx.EXPAND) 1654 1655 PNL_form.SetSizerAndFit(SZR_input) 1656 1657 # layout page 1658 SZR_main = gmGuiHelpers.makePageTitle(self, self.__title) 1659 SZR_main.Add(PNL_form, 1, wx.EXPAND)
1660 #-------------------------------------------------------- 1661 # event handling 1662 #--------------------------------------------------------
1663 - def __register_interests(self):
1664 self.PRW_firstname.add_callback_on_lose_focus(self.on_name_set) 1665 self.PRW_country.add_callback_on_selection(self.on_country_selected) 1666 self.PRW_zip_code.add_callback_on_lose_focus(self.on_zip_set)
1667 #--------------------------------------------------------
1668 - def on_country_selected(self, data):
1669 """Set the states according to entered country.""" 1670 self.PRW_state.set_context(context=u'country', val=data) 1671 return True
1672 #--------------------------------------------------------
1673 - def on_name_set(self):
1674 """Set the gender according to entered firstname. 1675 1676 Matches are fetched from existing records in backend. 1677 """ 1678 firstname = self.PRW_firstname.GetValue().strip() 1679 rows, idx = gmPG2.run_ro_queries(queries = [{ 1680 'cmd': u"select gender from dem.name_gender_map where name ilike %s", 1681 'args': [firstname] 1682 }]) 1683 if len(rows) == 0: 1684 return True 1685 wx.CallAfter(self.PRW_gender.SetData, rows[0][0]) 1686 return True
1687 #--------------------------------------------------------
1688 - def on_zip_set(self):
1689 """Set the street, town, state and country according to entered zip code.""" 1690 zip_code = self.PRW_zip_code.GetValue().strip() 1691 self.PRW_street.set_context(context=u'zip', val=zip_code) 1692 self.PRW_town.set_context(context=u'zip', val=zip_code) 1693 self.PRW_state.set_context(context=u'zip', val=zip_code) 1694 self.PRW_country.set_context(context=u'zip', val=zip_code) 1695 return True
1696 #============================================================
1697 -class cNewPatientWizard(wx.wizard.Wizard):
1698 """ 1699 Wizard to create a new patient. 1700 1701 TODO: 1702 - write pages for different "themes" of patient creation 1703 - make it configurable which pages are loaded 1704 - make available sets of pages that apply to a country 1705 - make loading of some pages depend upon values in earlier pages, eg 1706 when the patient is female and older than 13 include a page about 1707 "female" data (number of kids etc) 1708 1709 FIXME: use: wizard.FindWindowById(wx.ID_FORWARD).Disable() 1710 """ 1711 #--------------------------------------------------------
1712 - def __init__(self, parent, title = _('Register new person'), subtitle = _('Basic demographic details') ):
1713 """ 1714 Creates a new instance of NewPatientWizard 1715 @param parent - The parent widget 1716 @type parent - A wx.Window instance 1717 """ 1718 id_wiz = wx.NewId() 1719 wx.wizard.Wizard.__init__(self, parent, id_wiz, title) #images.getWizTest1Bitmap() 1720 self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY) 1721 self.__subtitle = subtitle 1722 self.__do_layout()
1723 #--------------------------------------------------------
1724 - def RunWizard(self, activate=False):
1725 """Create new patient. 1726 1727 activate, too, if told to do so (and patient successfully created) 1728 """ 1729 while True: 1730 1731 if not wx.wizard.Wizard.RunWizard(self, self.basic_pat_details): 1732 return False 1733 1734 try: 1735 # retrieve DTD and create patient 1736 ident = create_identity_from_dtd(dtd = self.basic_pat_details.form_DTD) 1737 except: 1738 _log.exception('cannot add new patient - missing identity fields') 1739 gmGuiHelpers.gm_show_error ( 1740 _('Cannot create new patient.\n' 1741 'Missing parts of the identity.' 1742 ), 1743 _('Adding new patient') 1744 ) 1745 continue 1746 1747 update_identity_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 1748 1749 try: 1750 link_contacts_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 1751 except: 1752 _log.exception('cannot finalize new patient - missing address fields') 1753 gmGuiHelpers.gm_show_error ( 1754 _('Cannot add address for the new patient.\n' 1755 'You must either enter all of the address fields or\n' 1756 'none at all. The relevant fields are marked in yellow.\n' 1757 '\n' 1758 'You will need to add the address details in the\n' 1759 'demographics module.' 1760 ), 1761 _('Adding new patient') 1762 ) 1763 break 1764 1765 link_occupation_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 1766 1767 break 1768 1769 if activate: 1770 from Gnumed.wxpython import gmPatSearchWidgets 1771 gmPatSearchWidgets.set_active_patient(patient = ident) 1772 1773 return ident
1774 #-------------------------------------------------------- 1775 # internal helpers 1776 #--------------------------------------------------------
1777 - def __do_layout(self):
1778 """Arrange widgets. 1779 """ 1780 # Create the wizard pages 1781 self.basic_pat_details = cBasicPatDetailsPage(self, self.__subtitle ) 1782 self.FitToPage(self.basic_pat_details)
1783 #============================================================ 1784 #============================================================
1785 -class cBasicPatDetailsPageValidator(wx.PyValidator):
1786 """ 1787 This validator is used to ensure that the user has entered all 1788 the required conditional values in the page (eg., to properly 1789 create an address, all the related fields must be filled). 1790 """ 1791 #--------------------------------------------------------
1792 - def __init__(self, dtd):
1793 """ 1794 Validator initialization. 1795 @param dtd The object containing the data model. 1796 @type dtd A cFormDTD instance 1797 """ 1798 # initialize parent class 1799 wx.PyValidator.__init__(self) 1800 # validator's storage object 1801 self.form_DTD = dtd
1802 #--------------------------------------------------------
1803 - def Clone(self):
1804 """ 1805 Standard cloner. 1806 Note that every validator must implement the Clone() method. 1807 """ 1808 return cBasicPatDetailsPageValidator(dtd = self.form_DTD) # FIXME: probably need new instance of DTD ?
1809 #--------------------------------------------------------
1810 - def Validate(self, parent = None):
1811 """ 1812 Validate the contents of the given text control. 1813 """ 1814 _pnl_form = self.GetWindow().GetParent() 1815 1816 error = False 1817 1818 # name fields 1819 if _pnl_form.PRW_lastname.GetValue().strip() == '': 1820 error = True 1821 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.')) 1822 _pnl_form.PRW_lastname.SetBackgroundColour('pink') 1823 _pnl_form.PRW_lastname.Refresh() 1824 else: 1825 _pnl_form.PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1826 _pnl_form.PRW_lastname.Refresh() 1827 1828 if _pnl_form.PRW_firstname.GetValue().strip() == '': 1829 error = True 1830 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.')) 1831 _pnl_form.PRW_firstname.SetBackgroundColour('pink') 1832 _pnl_form.PRW_firstname.Refresh() 1833 else: 1834 _pnl_form.PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1835 _pnl_form.PRW_firstname.Refresh() 1836 1837 # gender 1838 if _pnl_form.PRW_gender.GetData() is None: 1839 error = True 1840 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.')) 1841 _pnl_form.PRW_gender.SetBackgroundColour('pink') 1842 _pnl_form.PRW_gender.Refresh() 1843 else: 1844 _pnl_form.PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1845 _pnl_form.PRW_gender.Refresh() 1846 1847 # dob validation 1848 if ( 1849 (_pnl_form.PRW_dob.GetValue().strip() == u'') 1850 or (not _pnl_form.PRW_dob.is_valid_timestamp()) 1851 or (_pnl_form.PRW_dob.GetData().timestamp.year < 1900) 1852 ): 1853 error = True 1854 msg = _('Cannot parse <%s> into proper timestamp.') % _pnl_form.PRW_dob.GetValue() 1855 gmDispatcher.send(signal = 'statustext', msg = msg) 1856 _pnl_form.PRW_dob.SetBackgroundColour('pink') 1857 _pnl_form.PRW_dob.Refresh() 1858 else: 1859 _pnl_form.PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1860 _pnl_form.PRW_dob.Refresh() 1861 1862 # address 1863 is_any_field_filled = False 1864 address_fields = ( 1865 _pnl_form.TTC_address_number, 1866 _pnl_form.PRW_zip_code, 1867 _pnl_form.PRW_street, 1868 _pnl_form.PRW_town 1869 ) 1870 for field in address_fields: 1871 if field.GetValue().strip() == u'': 1872 if is_any_field_filled: 1873 error = True 1874 msg = _('To properly create an address, all the related fields must be filled in.') 1875 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 1876 field.SetBackgroundColour('pink') 1877 field.SetFocus() 1878 field.Refresh() 1879 else: 1880 is_any_field_filled = True 1881 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1882 field.Refresh() 1883 1884 address_fields = ( 1885 _pnl_form.PRW_state, 1886 _pnl_form.PRW_country 1887 ) 1888 for field in address_fields: 1889 if field.GetData() is None: 1890 if is_any_field_filled: 1891 error = True 1892 msg = _('To properly create an address, all the related fields must be filled in.') 1893 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 1894 field.SetBackgroundColour('pink') 1895 field.SetFocus() 1896 field.Refresh() 1897 else: 1898 is_any_field_filled = True 1899 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1900 field.Refresh() 1901 1902 return (not error)
1903 #--------------------------------------------------------
1904 - def TransferToWindow(self):
1905 """ 1906 Transfer data from validator to window. 1907 The default implementation returns False, indicating that an error 1908 occurred. We simply return True, as we don't do any data transfer. 1909 """ 1910 _pnl_form = self.GetWindow().GetParent() 1911 # fill in controls with values from self.form_DTD 1912 _pnl_form.PRW_gender.SetData(self.form_DTD['gender']) 1913 _pnl_form.PRW_dob.SetText(self.form_DTD['dob']) 1914 _pnl_form.PRW_lastname.SetText(self.form_DTD['lastnames']) 1915 _pnl_form.PRW_firstname.SetText(self.form_DTD['firstnames']) 1916 _pnl_form.PRW_title.SetText(self.form_DTD['title']) 1917 _pnl_form.PRW_nick.SetText(self.form_DTD['nick']) 1918 _pnl_form.PRW_occupation.SetText(self.form_DTD['occupation']) 1919 _pnl_form.TTC_address_number.SetValue(self.form_DTD['address_number']) 1920 _pnl_form.PRW_street.SetText(self.form_DTD['street']) 1921 _pnl_form.PRW_zip_code.SetText(self.form_DTD['zip_code']) 1922 _pnl_form.PRW_town.SetText(self.form_DTD['town']) 1923 _pnl_form.PRW_state.SetData(self.form_DTD['state']) 1924 _pnl_form.PRW_country.SetData(self.form_DTD['country']) 1925 _pnl_form.TTC_phone.SetValue(self.form_DTD['phone']) 1926 _pnl_form.TCTRL_comment.SetValue(self.form_DTD['comment']) 1927 return True # Prevent wxDialog from complaining
1928 #--------------------------------------------------------
1929 - def TransferFromWindow(self):
1930 """ 1931 Transfer data from window to validator. 1932 The default implementation returns False, indicating that an error 1933 occurred. We simply return True, as we don't do any data transfer. 1934 """ 1935 # FIXME: should be called automatically 1936 if not self.GetWindow().GetParent().Validate(): 1937 return False 1938 try: 1939 _pnl_form = self.GetWindow().GetParent() 1940 # fill in self.form_DTD with values from controls 1941 self.form_DTD['gender'] = _pnl_form.PRW_gender.GetData() 1942 self.form_DTD['dob'] = _pnl_form.PRW_dob.GetData() 1943 1944 self.form_DTD['lastnames'] = _pnl_form.PRW_lastname.GetValue() 1945 self.form_DTD['firstnames'] = _pnl_form.PRW_firstname.GetValue() 1946 self.form_DTD['title'] = _pnl_form.PRW_title.GetValue() 1947 self.form_DTD['nick'] = _pnl_form.PRW_nick.GetValue() 1948 1949 self.form_DTD['occupation'] = _pnl_form.PRW_occupation.GetValue() 1950 1951 self.form_DTD['address_number'] = _pnl_form.TTC_address_number.GetValue() 1952 self.form_DTD['street'] = _pnl_form.PRW_street.GetValue() 1953 self.form_DTD['zip_code'] = _pnl_form.PRW_zip_code.GetValue() 1954 self.form_DTD['town'] = _pnl_form.PRW_town.GetValue() 1955 self.form_DTD['state'] = _pnl_form.PRW_state.GetData() 1956 self.form_DTD['country'] = _pnl_form.PRW_country.GetData() 1957 1958 self.form_DTD['phone'] = _pnl_form.TTC_phone.GetValue() 1959 1960 self.form_DTD['comment'] = _pnl_form.TCTRL_comment.GetValue() 1961 except: 1962 return False 1963 return True
1964 #============================================================ 1965 # patient demographics editing classes 1966 #============================================================
1967 -class cPersonDemographicsEditorNb(wx.Notebook):
1968 """Notebook displaying demographics editing pages: 1969 1970 - Identity 1971 - Contacts (addresses, phone numbers, etc) 1972 - Social Network (significant others, GP, etc) 1973 1974 Does NOT act on/listen to the current patient. 1975 """ 1976 #--------------------------------------------------------
1977 - def __init__(self, parent, id):
1978 1979 wx.Notebook.__init__ ( 1980 self, 1981 parent = parent, 1982 id = id, 1983 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER, 1984 name = self.__class__.__name__ 1985 ) 1986 1987 self.__identity = None 1988 self.__do_layout() 1989 self.SetSelection(0)
1990 #-------------------------------------------------------- 1991 # public API 1992 #--------------------------------------------------------
1993 - def refresh(self):
1994 """Populate fields in pages with data from model.""" 1995 for page_idx in range(self.GetPageCount()): 1996 page = self.GetPage(page_idx) 1997 page.identity = self.__identity 1998 1999 return True
2000 #-------------------------------------------------------- 2001 # internal API 2002 #--------------------------------------------------------
2003 - def __do_layout(self):
2004 """Build patient edition notebook pages.""" 2005 2006 # contacts page 2007 new_page = gmPersonContactWidgets.cPersonContactsManagerPnl(self, -1) 2008 new_page.identity = self.__identity 2009 self.AddPage ( 2010 page = new_page, 2011 text = _('Contacts'), 2012 select = True 2013 ) 2014 2015 # identity page 2016 new_page = cPersonIdentityManagerPnl(self, -1) 2017 new_page.identity = self.__identity 2018 self.AddPage ( 2019 page = new_page, 2020 text = _('Identity'), 2021 select = False 2022 ) 2023 2024 # social network page 2025 new_page = cPersonSocialNetworkManagerPnl(self, -1) 2026 new_page.identity = self.__identity 2027 self.AddPage ( 2028 page = new_page, 2029 text = _('Social Network'), 2030 select = False 2031 )
2032 #-------------------------------------------------------- 2033 # properties 2034 #--------------------------------------------------------
2035 - def _get_identity(self):
2036 return self.__identity
2037
2038 - def _set_identity(self, identity):
2039 self.__identity = identity
2040 2041 identity = property(_get_identity, _set_identity)
2042 #============================================================ 2043 # old occupation widgets 2044 #============================================================ 2045 # FIXME: support multiple occupations 2046 # FIXME: redo with wxGlade 2047
2048 -class cPatOccupationsPanel(wx.Panel):
2049 """Page containing patient occupations edition fields. 2050 """
2051 - def __init__(self, parent, id, ident=None):
2052 """ 2053 Creates a new instance of BasicPatDetailsPage 2054 @param parent - The parent widget 2055 @type parent - A wx.Window instance 2056 @param id - The widget id 2057 @type id - An integer 2058 """ 2059 wx.Panel.__init__(self, parent, id) 2060 self.__ident = ident 2061 self.__do_layout()
2062 #--------------------------------------------------------
2063 - def __do_layout(self):
2064 PNL_form = wx.Panel(self, -1) 2065 # occupation 2066 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation')) 2067 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1) 2068 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient")) 2069 # known since 2070 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated')) 2071 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY) 2072 2073 # layout input widgets 2074 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4) 2075 SZR_input.AddGrowableCol(1) 2076 SZR_input.Add(STT_occupation, 0, wx.SHAPED) 2077 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND) 2078 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED) 2079 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND) 2080 PNL_form.SetSizerAndFit(SZR_input) 2081 2082 # layout page 2083 SZR_main = wx.BoxSizer(wx.VERTICAL) 2084 SZR_main.Add(PNL_form, 1, wx.EXPAND) 2085 self.SetSizer(SZR_main)
2086 #--------------------------------------------------------
2087 - def set_identity(self, identity):
2088 return self.refresh(identity=identity)
2089 #--------------------------------------------------------
2090 - def refresh(self, identity=None):
2091 if identity is not None: 2092 self.__ident = identity 2093 jobs = self.__ident.get_occupations() 2094 if len(jobs) > 0: 2095 self.PRW_occupation.SetText(jobs[0]['l10n_occupation']) 2096 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y')) 2097 return True
2098 #--------------------------------------------------------
2099 - def save(self):
2100 if self.PRW_occupation.IsModified(): 2101 new_job = self.PRW_occupation.GetValue().strip() 2102 jobs = self.__ident.get_occupations() 2103 for job in jobs: 2104 if job['l10n_occupation'] == new_job: 2105 continue 2106 self.__ident.unlink_occupation(occupation = job['l10n_occupation']) 2107 self.__ident.link_occupation(occupation = new_job) 2108 return True
2109 #============================================================
2110 -class cNotebookedPatEditionPanel(wx.Panel, gmRegetMixin.cRegetOnPaintMixin):
2111 """Patient demographics plugin for main notebook. 2112 2113 Hosts another notebook with pages for Identity, Contacts, etc. 2114 2115 Acts on/listens to the currently active patient. 2116 """ 2117 #--------------------------------------------------------
2118 - def __init__(self, parent, id):
2119 wx.Panel.__init__ (self, parent = parent, id = id, style = wx.NO_BORDER) 2120 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 2121 self.__do_layout() 2122 self.__register_interests()
2123 #-------------------------------------------------------- 2124 # public API 2125 #-------------------------------------------------------- 2126 #-------------------------------------------------------- 2127 # internal helpers 2128 #--------------------------------------------------------
2129 - def __do_layout(self):
2130 """Arrange widgets.""" 2131 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1) 2132 2133 szr_main = wx.BoxSizer(wx.VERTICAL) 2134 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND) 2135 self.SetSizerAndFit(szr_main)
2136 #-------------------------------------------------------- 2137 # event handling 2138 #--------------------------------------------------------
2139 - def __register_interests(self):
2140 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 2141 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
2142 #--------------------------------------------------------
2143 - def _on_pre_patient_selection(self):
2144 self._schedule_data_reget()
2145 #--------------------------------------------------------
2146 - def _on_post_patient_selection(self):
2147 self._schedule_data_reget()
2148 #-------------------------------------------------------- 2149 # reget mixin API 2150 #--------------------------------------------------------
2151 - def _populate_with_data(self):
2152 """Populate fields in pages with data from model.""" 2153 pat = gmPerson.gmCurrentPatient() 2154 if pat.connected: 2155 self.__patient_notebook.identity = pat 2156 else: 2157 self.__patient_notebook.identity = None 2158 self.__patient_notebook.refresh() 2159 return True
2160 #============================================================
2161 -class TestWizardPanel(wx.Panel):
2162 """ 2163 Utility class to test the new patient wizard. 2164 """ 2165 #--------------------------------------------------------
2166 - def __init__(self, parent, id):
2167 """ 2168 Create a new instance of TestPanel. 2169 @param parent The parent widget 2170 @type parent A wx.Window instance 2171 """ 2172 wx.Panel.__init__(self, parent, id) 2173 wizard = cNewPatientWizard(self) 2174 print wizard.RunWizard()
2175 #============================================================ 2176 if __name__ == "__main__": 2177 2178 #--------------------------------------------------------
2179 - def test_organizer_pnl():
2180 app = wx.PyWidgetTester(size = (600, 400)) 2181 app.SetWidget(cKOrganizerSchedulePnl) 2182 app.MainLoop()
2183 #--------------------------------------------------------
2184 - def test_person_names_pnl():
2185 app = wx.PyWidgetTester(size = (600, 400)) 2186 widget = cPersonNamesManagerPnl(app.frame, -1) 2187 widget.identity = activate_patient() 2188 app.frame.Show(True) 2189 app.MainLoop()
2190 #--------------------------------------------------------
2191 - def test_person_ids_pnl():
2192 app = wx.PyWidgetTester(size = (600, 400)) 2193 widget = cPersonIDsManagerPnl(app.frame, -1) 2194 widget.identity = activate_patient() 2195 app.frame.Show(True) 2196 app.MainLoop()
2197 #--------------------------------------------------------
2198 - def test_pat_ids_pnl():
2199 app = wx.PyWidgetTester(size = (600, 400)) 2200 widget = cPersonIdentityManagerPnl(app.frame, -1) 2201 widget.identity = activate_patient() 2202 app.frame.Show(True) 2203 app.MainLoop()
2204 #--------------------------------------------------------
2205 - def test_name_ea_pnl():
2206 app = wx.PyWidgetTester(size = (600, 400)) 2207 app.SetWidget(cNameGenderDOBEditAreaPnl, name = activate_patient().get_active_name()) 2208 app.MainLoop()
2209 #--------------------------------------------------------
2210 - def test_pat_contacts_pnl():
2211 app = wx.PyWidgetTester(size = (600, 400)) 2212 widget = cPersonContactsManagerPnl(app.frame, -1) 2213 widget.identity = activate_patient() 2214 app.frame.Show(True) 2215 app.MainLoop()
2216 #--------------------------------------------------------
2217 - def test_cPersonDemographicsEditorNb():
2218 app = wx.PyWidgetTester(size = (600, 400)) 2219 widget = cPersonDemographicsEditorNb(app.frame, -1) 2220 widget.identity = activate_patient() 2221 widget.refresh() 2222 app.frame.Show(True) 2223 app.MainLoop()
2224 #--------------------------------------------------------
2225 - def activate_patient():
2226 patient = gmPerson.ask_for_patient() 2227 if patient is None: 2228 print "No patient. Exiting gracefully..." 2229 sys.exit(0) 2230 from Gnumed.wxpython import gmPatSearchWidgets 2231 gmPatSearchWidgets.set_active_patient(patient=patient) 2232 return patient
2233 #-------------------------------------------------------- 2234 if len(sys.argv) > 1 and sys.argv[1] == 'test': 2235 2236 gmI18N.activate_locale() 2237 gmI18N.install_domain(domain='gnumed') 2238 gmPG2.get_connection() 2239 2240 # a = cFormDTD(fields = cBasicPatDetailsPage.form_fields) 2241 2242 # app = wx.PyWidgetTester(size = (400, 300)) 2243 # app.SetWidget(cNotebookedPatEditionPanel, -1) 2244 # app.SetWidget(TestWizardPanel, -1) 2245 # app.frame.Show(True) 2246 # app.MainLoop() 2247 2248 # phrasewheels 2249 # test_zipcode_prw() 2250 # test_state_prw() 2251 # test_street_prw() 2252 # test_organizer_pnl() 2253 #test_address_type_prw() 2254 #test_suburb_prw() 2255 test_urb_prw() 2256 #test_address_prw() 2257 2258 # contacts related widgets 2259 #test_address_ea_pnl() 2260 #test_person_adrs_pnl() 2261 #test_person_comms_pnl() 2262 #test_pat_contacts_pnl() 2263 2264 # identity related widgets 2265 #test_person_names_pnl() 2266 #test_person_ids_pnl() 2267 #test_pat_ids_pnl() 2268 #test_name_ea_pnl() 2269 2270 #test_cPersonDemographicsEditorNb() 2271 2272 #============================================================ 2273