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

Source Code for Module Gnumed.wxpython.gmDemographicsWidgets

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