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