Package Gnumed :: Package business :: Module gmOrganization
[frames] | no frames]

Source Code for Module Gnumed.business.gmOrganization

   1  """ 
   2  data objects for organization. Hoping to use the helper class to act as Facade 
   3  for aggregated data objects ? with validation rules.  
   4  re-used working code form gmClinItem and followed Script Module layout of gmEMRStructItems. 
   5   
   6  license: GPL""" 
   7  #============================================================ 
   8  __version__ = "$Revision: 1.40 $" 
   9   
  10  from Gnumed.pycommon import gmExceptions, gmBorg, gmPG 
  11  from Gnumed.business import gmDemographicRecord, gmPerson 
  12   
  13  import inspect 
  14   
  15  if __name__ == '__main__': 
  16          _ = lambda x:x 
  17   
  18  _log = logging.getLogger('gm.demos') 
  19  _log.info(__version__) 
  20   
  21   
  22  attrNames = [ 'name', 'office', 'subtype', 'memo','category', 'phone', 'fax', 'email', 'mobile' ] 
  23  addressNames = [ 'number', 'street', 'urb', 'postcode', 'state', 'country'] 
  24           
  25  commtypes = {  
  26  #       "email":gmDemographicRecord.EMAIL,  
  27  #       "fax":gmDemographicRecord.FAX,  
  28          #gmDemographicRecord.HOME_PHONE,  
  29  #       "phone":gmDemographicRecord.WORK_PHONE, 
  30  #       "web":gmDemographicRecord.WEB,  
  31  #       "mobile":gmDemographicRecord.MOBILE, 
  32  #       "jabber":gmDemographicRecord.JABBER  
  33          } 
  34   
  35  commnames = dict( [ (v,k) for (k,v) in commtypes.items()] ) 
  36   
  37  workAddressType = 2 # seems constant for gnumed schema in any language 
  38   
  39  #addressTypes = gmDemographicRecord.getAddressTypes() 
  40   
41 -class cCatFinder(gmBorg.cBorg):
42
43 - def __init__(self, categoryType = None, pkCol = 'id', nameCol = 'description'):
44 gmBorg.cBorg.__init__(self) 45 if not self.__dict__.has_key("categories"): 46 self.categories = {} 47 48 if categoryType == None: 49 return 50 51 if not self.categories.has_key(categoryType): 52 self.categories[categoryType] = {'toId': {}, 'toDescription': {},'id': pkCol, 'name': nameCol } 53 self.reload(categoryType)
54 55 56
57 - def reload(self, categoryType):
58 self.__init__(categoryType) 59 pk = self.categories[categoryType]['id'] 60 name = self.categories[categoryType]['name'] 61 result = gmPG.run_ro_query("personalia","select %s, %s from %s" % (pk, name, categoryType)) 62 if result is None: 63 _log.error("failed to load %s" % categoryType) 64 65 for (id, description) in result: 66 self.categories[categoryType]['toId'][description] = id 67 self.categories[categoryType]['toDescription'][id] = description 68 return self.categories[categoryType]
69 70 71
72 - def getId(self, categoryType, category):
73 return self.categories.get(categoryType, self.reload(categoryType)).get('toId',{}).get(category, None)
74
75 - def getCategory(self, categoryType, id):
76 return self.categories.get(categoryType, self.reload(categoryType)).get('toDescription',{}).get(id, "")
77
78 - def getCategories(self, categoryType):
79 return self.categories.get(categoryType, self.reload(categoryType)).get('toId',{}).keys()
80 81 #cCatFinder('org_category') 82 #cCatFinder('enum_comm_types') 83 #cCatFinder('occupation', 'id', 'name') 84 85 DEPARTMENT = 1 86 87 88 89
90 -class cOrg:
91
92 - def __init__(self):
93 pass
94
95 - def getId(self):
96 return None
97
98 - def setId(self, id): # ? change to non-public
99 pass
100
101 - def set(self, name, office, subtype, memo, category, phone, fax, email,mobile = ""):
102 pass
103
104 - def setAddress(self, number, street, urb, postcode, state, country):
105 pass
106
107 - def getAddress(self):
108 return {}
109
110 - def __setitem__(self, k, v):
111 pass
112
113 - def __getitem__(self, k):
114 return None
115 116
117 - def get(self):
118 return [] 119
120 - def load(self, pk):
121 return False
122
123 - def save(self):
124 return False
125 126
127 - def getParent(self):
128 return None
129 130 131
132 -class cOrgHelper:
133 #----------------------------- 134 # class functions 135 #---------------------------------------------------------------
136 - def __init__(self):
137 pass
138
139 - def findAllOrganizations(self):
140 return []
141
142 - def findOrgsForIds( self,id_list):
143 return []
144
146 return [ (0,"") ]
147
148 - def updateCache(self,org):
149 pass
150 - def cacheContains(self, id):
151 return False
152
153 - def getFromCache(self,id):
154 return None
155
156 - def removeFromCache(self, id):
157 return False
158
159 - def create(self):
160 return None
161
162 - def isOrgPerson(self, org):
163 return False
164
165 - def isPerson(self, org):
166 return False
167
168 - def setXMLClipboardFormat(self):
169 pass
170
172 pass
173 174 175 176
177 -class cOrgHelperImpl1(gmBorg.cBorg, cOrgHelper):
178
179 - def __init__(self):
180 gmBorg.cBorg.__init__(self) 181 cOrgHelper.__init__(self) 182 self._cache = {} 183 self.setLineSeparatedClipboardFormat()
184 185
187 self._clipboardFormat = "%(name)s,\n%(address_str)s,\nphone:%(phone)s,\nfax:%(fax)s\nemail: %(email)s\nref:%(orgtype)s/%(id)d\n"
188
189 - def setXMLClipboardFormat(self):
190 self._clipboardFormat = """ 191 <%(orgtype)s id='%(id)d'> 192 <name>%(name)s</name> 193 <address>%(address_str)s</address> 194 <phone>%(phone)s</phone> 195 <fax>%(fax)s</fax> 196 <email>%(email)s</email> 197 </(orgtype)s> 198 """
199
200 - def getClipboardText(self, org):
201 202 d = { 'name': org['name'], 203 'address_str': self.getAddressStr(org), 204 'phone' : org['phone'], 205 'fax' : org['fax'], 206 'email' : org['email'], 207 'id': org.getId() 208 } 209 if self.isPerson(org): 210 d['orgtype'] = 'person' 211 212 elif org.getParent() <> None: 213 d['orgtype'] = 'org' 214 d['name'] = ' '.join( [ org['name'], org['subtype'], ',',org.getParent()['name'] ] ) 215 else: 216 d['orgtype'] = 'org' 217 218 # find a non-blank address 219 o = org 220 while o.getParent() <> None and self.getAddressStr(o).strip() == '': 221 d['address_str'] = self.getAddressStr(o.getParent() ) 222 o = o.getParent() 223 224 str = self._clipboardFormat % d 225 226 return str
227
228 - def cacheContains(self,id):
229 return self._cache.has_key(id)
230
231 - def getFromCache(self,id):
232 return self._cache.get(id, None)
233
234 - def updateCache(self,org):
235 self._cache[org.getId()] = org
236
237 - def removeFromCache(self, id):
238 if self._cache.has_key(id): 239 del self._cache[id] 240 return True 241 return False
242
243 - def findAllOrganizations(self):
244 result = gmPG.run_ro_query("personalia", """select id from dem.org""",[]) 245 if result == None: 246 _log.exception("Unable to select id from org") 247 return False 248 249 ids = [ x for [x] in result] 250 return self.findOrgsForIds(ids)
251 252
253 - def findOrgsForIds(self, ids):
254 """ finds org objects by id. returns a list of cOrgHelper objects 255 """ 256 #TODO - caching of org objects on class level. 257 # - listening for backend data changes using pycommon.gmDispatcher 258 # and cache update. 259 return self._findOrgsForIdsCacheCheck(ids)
260 261 262
263 - def _findOrgsForIdsCacheCheck(self, ids):
264 orglist = [] 265 fetch_ids = [] 266 for id in ids: 267 if self.cacheContains(id): 268 org = self.getFromCache(id) 269 orglist.append(org) 270 continue 271 fetch_ids.append(id) 272 273 dbOrgList = self._findOrgsForIdsFromDB( fetch_ids) 274 275 for org in dbOrgList: 276 self.updateCache(org) 277 278 return orglist + dbOrgList
279
280 - def _findOrgsForIdsFromDB(self, fetch_ids):
281 if fetch_ids == None or fetch_ids == []: 282 return [] 283 284 om = get_org_data_for_org_ids(fetch_ids) 285 cm = get_comm_channels_data_for_org_ids(fetch_ids ) 286 am = get_address_data_for_org_ids( fetch_ids) 287 m = {} 288 orglist = [] 289 for id in fetch_ids: 290 org = self.create() 291 if not org._load_org_from_tuple(om.get(id, None), id): 292 continue 293 org._load_comm_channels_from_tuples( cm.get(id, None) ) 294 org._load_address_from_tuple( am.get(id, None) ) 295 orglist.append(org) 296 return orglist
297
298 - def findOrgsByName( self, name, exact = False):
299 """the org name is a unique key, so should only return one or none org""" 300 if exact: query= "select id from dem.org where description = '%s'"%name 301 else: query = "select id from dem.org where description like '%s%%'"%name 302 303 result = gmPG.run_ro_query("personalia", query ) 304 if result is None: 305 _log.error("Unable to find org by name %s" % name) 306 return [None] 307 308 return self.findOrgsForIds([ x[0] for x in result])
309 310
311 - def findAllOrganizationPKAndName(self):
312 return [ (0,"") ]
313 314
315 - def create(self):
316 return cOrgImpl1()
317 318
319 - def getAddressStr(self, org):
320 a = org.getAddress() 321 return " ".join( [a.get('number','').strip(), a.get('street','').strip(), a.get('urb','').strip(), a.get('postcode','')])
322 323 324
325 -class cOrgHelperImpl2(cOrgHelperImpl1):
326
327 - def __init__(self):
329
330 - def create(self):
331 return cCompositeOrgImpl1()
332
333 - def findOrgsForIds(self, ids):
334 """extends cOrgHelperImpl1's findOrgsforIds and orders them 335 parent/ child order""" 336 337 l = cOrgHelperImpl1.findOrgsForIds(self, ids) 338 childMap = {} 339 parents = filter( lambda(o): o.getParent() is None, l) 340 childMap = dict( [ (p.getId() , []) for p in parents ] ) 341 for o in l: 342 if o in parents: 343 continue 344 childMap[o.getParent().getId()].append(o) 345 346 l2 = [] 347 for p in parents: 348 l2.append(p) 349 for c in childMap[p.getId()]: 350 l2.append(c) 351 352 return l2
353 354
355 -class _cPersonMarker:
356 """marker class, for person type check""" 357 pass
358
359 -class cOrgHelperImpl3(cOrgHelperImpl2):
360 """extends org/suborg handling of cOrgHelperImpl2 to handle org persons""" 361
362 - def __init__(self):
364
365 - def create(self):
366 return cOrgHelperImpl2.create(self)
367
368 - def createOrgPerson(self):
369 return cOrgDemographicAdapter()
370
371 - def isPersonOrg(self, org):
372 return _cPersonMarker in inspect.getmro(org.__class__)
373
374 - def isPerson(self, org):
375 return self.isPersonOrg(org)
376
377 -class cOrgImpl1(cOrg):
378 379 _cache = {} 380 #----------------------------------------- 381 # instance methods 382 #-------------------------------------------------------------------------- 383
384 - def __init__(self, helper = cOrgHelperImpl1() ):
385 self._map = dict ( [ (n,'') for n in attrNames] ) 386 387 self._changed= {} 388 self.pk = None 389 self._addressModified(False) 390 self._address = {} 391 392 self._personMap = {} 393 self._helper = helper 394 pass
395
396 - def getHelper(self):
397 return self._helper
398
399 - def getId(self):
400 return self.pk
401
402 - def setId(self, pk):
403 self.pk = pk
404
405 - def getAddress(self):
406 return self.getAddressDict()
407
408 - def getAddressDict(self):
409 d = {} 410 d.update(self._address) 411 return d
412
413 - def setAddress(self, number, street, urb, postcode, state, country):
414 self._setAddressImpl( number, street, urb, postcode, state, country)
415
416 - def _setAddressImpl( self, *kwrd, **kargs):
417 names = addressNames 418 if kargs == {} and kwrd <> []: 419 kargs = dict( [ (a, v) for a,v in zip( names, kwrd) ] ) 420 421 422 for k in names: 423 a = self._address 424 if a.get(k, None) <> kargs.get(k, None): 425 self._addressModified(True) 426 a[k] = kargs[k]
427 428
429 - def _addressModified(self, val = None):
430 if val <> None: 431 self._amodified = val 432 return self._amodified
433
434 - def set(self, name, office, subtype, memo, category, phone, fax, email,mobile):
435 self._set_impl(name, office, subtype, memo, category, phone, fax, email,mobile)
436 437
438 - def _set_impl(self, *kwrd, **kargs):
439 """ 440 441 """ 442 n = attrNames 443 if kargs == {} and kwrd <> []: 444 kargs = dict( [ (a, v) for a,v in zip( n, kwrd) ] ) 445 446 changed = {} 447 for k in n: 448 v = self._map.get(k, None) 449 450 if v != kargs[k]: 451 changed[k] = kargs[k] 452 453 self._changed = changed
454
455 - def __setitem__(self, k, v):
456 if k in attrNames and self._map.get(k, None) <> v: 457 self._changed[k] = v
458
459 - def __getitem__(self, k):
460 v = self._changed.get(k, None) 461 if v == None: 462 v = self._map.get(k, None) 463 464 return v
465 466
467 - def _save_comm_channels(self):
468 if self.getId() is None: 469 _log.error("Unable to save comm channel %s : %s due to no org id" % (k,v) ) 470 return False 471 472 comm_changes = {} 473 for k,id_type in commtypes. items(): 474 if self._changed.has_key(k): 475 comm_changes[id_type] = self._changed[k] 476 477 urls = comm_changes.values() 478 if urls == []: 479 return True 480 481 places = ['%s'] *len(urls) 482 483 format = ', '.join(places) 484 485 cmd = [ 486 ("""select id, url, id_type from dem.comm_channel where url in( %s )""" % format, urls) ] 487 result = gmPG.run_commit('personalia', cmd) 488 if result is None: 489 _log.error("find existing org comms failed" ) 490 return False 491 492 493 existing_urls = dict( [ (url,(id, id_type) ) for (id, url, id_type) in result] ) 494 for id_type , url in comm_changes.items(): 495 if url in existing_urls.keys() and existing_urls[url][1] <> id_type: 496 _log.warning("Existing comm url mismatches type for org url %s, inserting same url different type!" % url) 497 del existing_urls[url] 498 cmds = [] 499 500 delete_link_cmd = """delete from dem.lnk_org2comm_channel 501 where id_comm in ( 502 select l2.id_comm from 503 dem.lnk_org2comm_channel l2 , dem.comm_channel c 504 where c.id = l2.id_comm 505 and c.id_type = %d 506 and l2.id_org = %d 507 ) """ 508 509 for url in existing_urls.keys(): 510 (id_comm, id_type) = existing_urls[url] 511 cmds = [ (delete_link_cmd % (id_type, self.getId()) ,[] ), 512 ("""insert into dem.lnk_org2comm_channel( id_comm, id_org) 513 values ( %d, %d ) """ % ( id_comm, self.getId() ) , [] ) 514 ] 515 516 for id_type, url in comm_changes.items(): 517 if url in existing_urls.keys(): 518 continue 519 520 if url.strip() == "": 521 cmds.append( 522 (delete_link_cmd %(id_type, self.getId()) , [] ) 523 ) 524 else: 525 526 cmds.append( 527 ("""insert into dem.comm_channel( url, id_type) 528 values( '%s', %d)""" % (url, id_type),[] ) 529 ) 530 cmds.append( 531 ("""insert into dem.lnk_org2comm_channel(id_comm, id_org) 532 values( currval('comm_channel_id_seq'), %d)""" % 533 self.getId() ,[] ) ) 534 535 536 result = gmPG.run_commit('personalia',cmds)
537
538 - def _save_address(self):
539 a = self._address 540 541 if not self._addressModified(): 542 return True 543 544 # allow for no address 545 if a['urb'].strip() == '': 546 return True 547 548 return self.linkNewAddress(a['number'],a['street'], a['urb'], a['postcode'], a.get('state', None), a.get('country', None) )
549 550 551
552 - def linkNewAddress (self, number, street, urb, postcode, state = None, country = None):
553 """Adds a new address into this org list of addresses. Basically cut and 554 paste and delete unnecessary fields from gmDemographics function. 555 """ 556 urb = urb.upper() 557 if state == "": state = None 558 if country == "": country = None 559 560 561 562 if state is None: 563 print "urb, postcode", urb, postcode 564 state, country = gmDemographicRecord.guess_state_country(urb, postcode) 565 print "state, country", state, country 566 # address already in database ? 567 cmd = """ 568 select addr_id from dem.v_basic_address 569 where 570 number = %s and 571 street = %s and 572 city = %s and 573 postcode = %s and 574 state = %s and 575 country = %s 576 """ 577 data = gmPG.run_ro_query ('personalia', cmd, None, number, street, urb, postcode, state, country) 578 if data is None: 579 s = " ".join( ( number, street, urb, postcode, state, country ) ) 580 _log.error('cannot check for address existence (%s)' % s) 581 return None 582 583 # delete any pre-existing link for this org 584 cmd = """ 585 delete from dem.lnk_person_org_address 586 where 587 id_org = %s 588 """ 589 gmPG.run_commit ('personalia', [(cmd, [self.getId()])]) 590 591 # yes, address already there, just add the link 592 if len(data) > 0: 593 addr_id = data[0][0] 594 cmd = """ 595 insert into dem.lnk_person_org_address (id_org, id_address) 596 values (%d, %d) 597 """ % (self.getId(), addr_id) 598 return gmPG.run_commit ("personalia", [ ( cmd,[]) ]) 599 600 # no, insert new address and link it, too 601 cmd1 = """ 602 insert into dem.v_basic_address (number, street, city, postcode, state, country) 603 values (%s, %s, %s, %s, %s, %s) 604 """ 605 cmd2 = """ 606 insert into dem.lnk_person_org_address (id_org, id_address) 607 values (%d, currval('address_id_seq')) 608 """ % self.getId() 609 return gmPG.run_commit ("personalia", [ 610 (cmd1, (number, street, urb, postcode, state, country)), 611 (cmd2, [] ) 612 ] 613 )
614 615 616
617 - def get(self):
618 m = {} 619 m.update(self._map) 620 m.update(self._changed) 621 return m 622 623
624 - def load(self, pk):
625 return ( self._load_org(pk) and 626 self._load_comm_channels() 627 and self._load_address() )
628 629 630 631
632 - def _load_org(self, pk):
633 m_org = get_org_data_for_org_ids( [pk] ) 634 if m_org == None or not m_org.has_key(pk): 635 #<DEBUG> 636 print "org id = ", pk, " not found" 637 #</DEBUG> 638 return False 639 self._load_org_from_tuple(m_org[pk], pk) 640 641
642 - def _load_org_from_tuple(self, tuple, pk = None):
643 if tuple == None or tuple == []: 644 self.setId(None) 645 return False 646 647 (description, id_category) = tuple 648 m=self._map 649 cf = cCatFinder() 650 m['category']=cf.getCategory("org_category",id_category) 651 652 m['name']=description 653 self.setId(pk) 654 655 return True
656 657
658 - def _load_comm_channels(self):
659 """uses get_comm_channels_data_for_org_ids with only a singleton id list, 660 with the current id to be fetched, then convert to self._map so 661 can be read from self.get() #returning a map of comm channel types vs urls""" 662 m = get_comm_channels_data_for_org_ids([ self.getId() ] ) 663 if m == None: 664 return False 665 666 if m.has_key(self.getId()): 667 return self._load_comm_channels_from_tuples(m[self.getId()])
668 669 670
671 - def _load_comm_channels_from_tuples(self, rows):
672 if rows == None : 673 return False 674 n = commnames 675 for ( id_type, url) in rows: 676 if commnames.has_key(int(id_type)): 677 self._map[commnames[id_type]] = url 678 679 return True
680
681 - def _load_address(self):
682 m = get_address_data_for_org_ids( [self.getId()]) 683 if m == None: 684 return False 685 686 if not m.has_key(self.getId() ): 687 _log.error("No address for org" ) 688 return True 689 690 return self._load_address_from_tuple( m[self.getId()] )
691 692
693 - def _load_address_from_tuple(self, r):
694 #precondition: must be a tuple and have right number of fields 695 if r == None or len(r) < 6: 696 return False 697 698 self._address = { 'number':r[0], 'street':r[1], 'urb':r[2], 'postcode':r[3], 'state':r[4], 'country': r[5] } 699 700 self._addressModified(False) 701 702 return True 703 704
705 - def shallow_del(self):
706 cmds = [ 707 ("delete from dem.lnk_person_org_address where id_org = %d"%self.getId() , [] ), 708 ("delete from dem.lnk_org2comm_channel where id_org = %d"%self.getId(),[] ), 709 ("delete from dem.org where id = %d"%self.getId() , [] ) 710 ] 711 712 if (gmPG.run_commit('personalia',cmds) == None): 713 _log.error("failed to remove org") 714 return False 715 716 self.setId(None) 717 718 return True
719 720 721 722
723 - def _create(self):
724 #<DEBUG> 725 #print "in _create" 726 #</DEBUG> 727 v = self['name'] 728 if v <> None: 729 cmd = "select id from dem.org where description = '%s'" % v 730 result = gmPG.run_ro_query('personalia', cmd) 731 if result <> None and len(result) <> 0: 732 self.setId(result[0][0]) 733 return True 734 735 736 cmd = ("""insert into dem.org (description, id_category) values('xxxDefaultxxx', ( select id from dem.org_category limit 1) )""", []) 737 cmd2 = ("""select currval('dem.org_id_seq')""", []) 738 result = gmPG.run_commit('personalia', [cmd, cmd2]) 739 if result is None: 740 cmd = ("""select id from dem.org where description ='xxxDefaultxxx'""",[]) 741 result = gmPG.run_commit('personalia', [cmd] ) 742 if result <> None and len(result) == 1: 743 self.setId(result[0][0]) 744 #<DEBUG> 745 #print "select id from org ->", self.getId() 746 #</DEBUG> 747 return True 748 return False 749 self.setId(result[0][0]) 750 #<DEBUG> 751 #print "from select currval -> ", self.getId() 752 #</DEBUG> 753 return True
754
755 - def save(self):
756 757 #TODO only the name, category attributes are saved; sql places for memo , office, subtype needed. 758 m={} 759 c = self._changed 760 m.update(self._map) 761 m.update(self._map) 762 m.update(c) 763 if not m.has_key('name') or m['name'].strip() =='': 764 print "PLEASE ENTER ORG NAME" #change this 765 return False 766 print "self.getId() = ", self.getId() , " is None : ", self.getId() is None 767 if self.getId() is None: 768 if not self._create(): 769 import sys 770 _log.error("Cannot create org") 771 return False 772 if self.getId() is None: 773 return False 774 # c is any changed, m is what is current 775 if c.has_key('name') or c.has_key('category'): 776 777 #print "pk = ", self.getId() 778 #org = cOrganization(str(self.getId())) 779 cf = cCatFinder() 780 #print "cCatFinder", cf.getCategories('org_category') 781 #print "m['category']", m['category'], "cf.getId(.. = ", cf.getId('org_category', m['category']) 782 cmd = """ 783 update dem.org set description='%s' , 784 id_category = %s where id = %s 785 """ % ( m['name'], 786 str( cf.getId('org_category', m['category']) ), 787 str(self.getId()) ) 788 result = gmPG.run_commit( "personalia", [ (cmd,[] ) ] ) 789 if result is None: 790 _log.error("Cannot save org") 791 return False 792 793 self._save_address() 794 self._save_comm_channels() 795 self._helper.updateCache(self) 796 return True
797
798 - def linkPerson( self, demRecord): # demRecord is a cDemographicRecord
799 if self.getId() == None: 800 return False, _( "Org must be saved before adding persons") 801 802 # not needed currently, but just in case 803 if demRecord.getID() is None: 804 return False, _("demRecord doesn't have an ID ! Impossible !") 805 806 self._personMap[int(demRecord.getID())] = demRecord 807 808 # checked already linked 809 cmd = "select id from dem.lnk_person_org_address where id_identity = %d and id_org = %d" % (int(demRecord.getID()), self.getId() ) 810 811 result = gmPG.run_ro_query("personalia", cmd,[]) 812 if not result is None and len(result) == 1: 813 return True, _("Ok") 814 815 cmd = "insert into dem.lnk_person_org_address(id_identity, id_org) values (%d,%d)" % ( int(demRecord.getID()), self.getId() ) 816 817 result = gmPG.run_commit("personalia", [ (cmd,[]) ] ) 818 819 if result is None: 820 _log.error("Cannot link person") 821 return False, _("SQL failed for link persons") 822 823 return True, _("Ok") 824
825 - def unlinkPerson(self, demographicRecord):
826 if self.getId() == None: 827 return False, _("Org must be saved before adding persons") 828 829 cmd = """delete from dem.lnk_person_org_address where id_identity = %d 830 and id_org = %d """ % ( int(demographicRecord.getID()) , self.getId() ) 831 832 result = gmPG.run_commit("personalia", [ (cmd,[]) ] ) 833 834 if result is None: 835 _log.error("Cannot unlink person") 836 return False 837 838 del self._personMap[demographicRecord.getID()] # unlink in cache as well 839 840 return True
841 842
843 - def getPersonMap(self, reload = True):
844 """gets the persons associated with this org, lazy loading demographic records 845 and caching if needed; need to later use a singleton demographic cache, 846 so that single copies of a demographic record is shared """ 847 if self.getId() == None: 848 return {} 849 850 m = {} 851 m.update(self._personMap) 852 853 if not reload and not self._personMap == {} : 854 return m 855 856 query = "select id_identity from dem.lnk_person_org_address where id_org = %d"% self.getId() 857 result = gmPG.run_ro_query("personalia", query) 858 print "for ", query, " got ", result 859 if result is None: 860 _log.error("Cannot search for org persons") 861 return None 862 863 ids = filter( lambda(t): t <> None, [ id for [id] in result ]) 864 print "id list is ", ids 865 new_ids = filter( lambda(id): id not in m.keys(), ids) 866 867 for id in new_ids: 868 rec = gmDemographicRecord.cDemographicRecord_SQL(id) 869 m[id] = rec 870 871 self._personMap.update(m) 872 873 return m
874 875
876 -class cCompositeOrgImpl1( cOrgImpl1):
877 """this class behaves differently from cOrgImpl1 iff there is a parent org""" 878
879 - def __init__(self, parent = None, helper = cOrgHelperImpl2() ):
880 cOrgImpl1.__init__(self, helper) 881 self._parent = parent
882
883 - def _create(self):
884 885 if not cOrgImpl1._create(self): 886 887 return False 888 889 return self._saveCompositeName()
890
891 - def save(self):
892 """if getParent() is None, then the behaviour is 893 unchanged from cOrgImpl1, but if there is a parent org, 894 then there will also sub-org information saved in the description""" 895 896 if not cOrgImpl1.save(self): 897 return False 898 return self._saveCompositeName()
899 900 901
902 - def _saveCompositeName(self):
903 parent = self.getParent() 904 if parent == None: 905 return True 906 907 new_description = '\n'.join([parent['name'] , self['name'], self['subtype']]) 908 result = gmPG.run_commit("personalia", [ ("""update dem.org set description='%s' where id=%d 909 """ % (new_description, self.getId() ), [] ) ]) 910 if result == None: 911 _log.exception("unable to update sub-org name") 912 return False 913 return True
914 915
916 - def _load_org_from_tuple(self, tuple, pk = None):
917 """this loads the org like cOrgImpl1, but then checks for 918 additional sub-org information in the 'name' aka org.description, 919 and if it exists, the parent is retrieved or constructed using 920 the findOrgByName function. 921 """ 922 923 if not cOrgImpl1._load_org_from_tuple(self, tuple, pk): 924 return False 925 926 # first extended behaviour, recognise subtype attribute. 927 self['subtype'] = '' 928 929 l = self['name'].split('\n') 930 print "split org name into ", l 931 if len(l) < 3: 932 return True 933 934 (parentName, self['name'], self['subtype'] ) = l 935 orgList = self._helper.findOrgsByName(parentName, exact = True) 936 if orgList == []: 937 return True 938 org = orgList[0] 939 self.setParent(org) 940 941 return True
942 943
944 - def getParent(self):
945 return self._parent
946
947 - def setParent(self, parent):
948 self._parent = parent
949 950 951 952
953 -class cOrgDemographicAdapter(cOrg, _cPersonMarker):
954
955 - def __init__(self, parent = None, helper = cOrgHelperImpl3()):
956 self._parent = parent 957 self._helper = helper 958 self._record = None 959 self._data = { 'name':'', 960 'subtype':'', 961 'memo':'', 962 'phone':'', 963 'fax':'', 964 'email':'', 965 'mobile': '' 966 } 967 968 self._address = { 969 'number':'', 970 'street':'', 971 'urb' :'', 972 'postcode': '', 973 'state' : None, 974 'country': None 975 }
976
977 - def getHelper(self):
978 return self._helper
979
980 - def setDemographicRecord(self, record):
981 self._record = record 982 self._parseRecord()
983
984 - def getDemographicRecord(self):
985 return self._record
986
987 - def getId(self):
988 if self._record is None: 989 return None 990 return self._record.getID()
991 992
993 - def setId(self, id): # ? change to non-public
994 pass
995
996 - def set(self, name, office, subtype, memo, category, phone, fax, email,mobile = ""):
997 d = self._data 998 s = { 'name':name, 999 'office': office, 1000 'subtype': subtype, 1001 'memo': memo, 1002 'category': category, 1003 'phone': phone, 1004 'fax' : fax, 1005 'email' : email, 1006 'mobile': mobile 1007 } 1008 for k in d.keys(): 1009 d[k] = s[k]
1010 #<DEBUG> 1011 #print 'data ', k, ' has been set to ', d[k] 1012 #</DEBUG> 1013 1014 1015 1016
1017 - def setAddress(self, number, street, urb, postcode, state, country):
1018 d = self._address 1019 s = { 'number': number, 1020 'street': street, 1021 'urb' : urb, 1022 'postcode' : postcode, 1023 'state' : state, 1024 'country': country 1025 } 1026 1027 1028 for k in s.keys(): 1029 d[k] = s[k]
1030 #<DEBUG> 1031 #print "self._address is now ", self._address 1032 #</DEBUG> 1033
1034 - def getAddress(self):
1035 m = {} 1036 m.update(self._address) 1037 return m
1038 1039 1040
1041 - def __setitem__(self, k, v):
1042 d = self._data 1043 if d.has_key(k): 1044 d[k] = v 1045 return True 1046 return False
1047
1048 - def __getitem__(self, k):
1049 d = self._data 1050 if d.has_key(k): 1051 return d[k] 1052 return None
1053 1054
1055 - def get(self):
1056 m = {} 1057 m.update(self._data) 1058 return m 1059
1060 - def load(self, pk):
1061 self.setDemographicRecord(gmDemographicRecord.cDemographicRecord_SQL(pk))
1062
1063 - def _parseRecord(self):
1064 d = self._data 1065 r = self._record 1066 n = r.get_names() 1067 if n['title'][-1] <> '.': 1068 n['title'] = n['title'] + '.' 1069 d['name'] = ' '.join([n['title'], n['firstnames'], n['lastnames'] ]) 1070 if r.getOccupation() : 1071 d['subtype'] = r.getOccupation() 1072 1073 for k,id in commtypes.items(): 1074 v = r.getCommChannel(id) 1075 if v: d[k] = v 1076 1077 addressTypes = gmDemographicRecord.getAddressTypes() 1078 address = r.getAddresses( addressTypes[workAddressType], firstonly=1) 1079 a = self._address 1080 #<DEBUG> 1081 print "got back address from demographic record", address 1082 #</DEBUG> 1083 if address is None: 1084 return 1085 1086 fields = ['number', 'street', 'urb', 'postcode'] 1087 if type(address) is type([]) and len(address) >0: 1088 if type(address[0]) is type({}): 1089 address = address[0] 1090 elif type(address[0]) is type(''): 1091 a = dict ( [(k,v) for k,v in zip( fields, address) ] ) 1092 return 1093 1094 for k in fields: 1095 if type(address) is type({}): 1096 a[k] = address.get(k, '')
1097 1098
1099 - def save(self):
1100 print "Called save on orgPersonAdapter" 1101 if self.getParent() is None: 1102 print "no parent" 1103 _log.error("This orgPersonAdapter needs a parent org") 1104 return False 1105 1106 if self.getId() is None: 1107 print "no id" 1108 if not self._create(): 1109 print "can't create an id" 1110 return False 1111 1112 1113 r = self._record 1114 d = self._data 1115 1116 print "splitting name" 1117 1118 l0 = d['name'].split('.') 1119 if len(l0) > 1: 1120 if len(l0) > 2: 1121 print "ambiguous title separation at '.'" 1122 title = l0[0] + '.' 1123 name = " ".join( l0[1:]) 1124 else: 1125 name = d['name'] 1126 title = '' 1127 1128 l1 = name.split(',') 1129 1130 # parse the name field 1131 if len(l1) == 2: 1132 # assume "lastnames , firstnames" format 1133 l = [ x.strip() for x in l1] 1134 first , last = l[1], l[0] 1135 else: 1136 l1 = name.split(' ') 1137 l = [ x.strip() for x in l1] 1138 # try the UPPER CASE IS LASTNAME starting from last word 1139 inUpper = -1 1140 while inUpper > -len(l) and l[inUpper - 1].isupper(): 1141 inUpper -= 1 1142 1143 first, last = ' '.join(l[0:inUpper]), ' '.join(l[inUpper:]) 1144 print "adding name" 1145 r.addName(first, last, True) 1146 r.setTitle(title) 1147 1148 if r.setOccupation( d['subtype']) is None: 1149 print "FAILED TO save occupation" 1150 print "record occupation is ", r.getOccupation() 1151 1152 for k in commtypes.keys(): 1153 v = d.get(k,'') 1154 if v is None or v.strip() == '': 1155 continue 1156 t = commtypes[k] 1157 r.linkCommChannel( t, v) 1158 1159 1160 a = self._address 1161 1162 if a['urb'].strip() <> '' and a['street'].strip() <> '': 1163 r.linkNewAddress( addressTypes[workAddressType], 1164 a['number'], 1165 a['street'], 1166 a['urb'], 1167 a['postcode'] ) 1168 1169 self.getParent().linkPerson(self.getDemographicRecord()) 1170 return True
1171 1172
1173 - def _create(self):
1174 id = gmPerson.create_dummy_identity() 1175 if id is None: 1176 return False 1177 self._record = gmDemographicRecord.cDemographicRecord_SQL(id) 1178 return True
1179
1180 - def getParent(self):
1181 return self._parent
1182
1183 - def setParent(self, parent):
1184 self._parent = parent
1185 1186 1187 1188
1189 -def get_comm_channels_data_for_org_ids( idList):
1190 """gets comm_channels for a list of org_id. 1191 returns a map keyed by org_id with lists of comm_channel data (url, type). 1192 this allows a single fetch of comm_channel data for multiple orgs""" 1193 1194 ids = ", ".join( [ str(x) for x in idList]) 1195 cmd = """select l.id_org, id_type, url 1196 from dem.comm_channel c, dem.lnk_org2comm_channel l 1197 where 1198 c.id = l.id_comm and 1199 l.id_org in ( select id from dem.org where id in (%s) ) 1200 """ % ids 1201 result = gmPG.run_ro_query("personalia", cmd) 1202 if result == None: 1203 _log.error("Unable to load comm channels for org" ) 1204 return None 1205 m = {} 1206 for (id_org, id_type, url) in result: 1207 if not m.has_key(id_org): 1208 m[id_org] = [] 1209 m[id_org].append( (id_type, url) ) 1210 1211 return m # is a Map[id_org] = list of comm_channel data 1212
1213 -def get_address_data_for_org_ids( idList):
1214 """gets addresses for a list of valid id values for orgs. 1215 returns a map keyed by org_id with the address data 1216 """ 1217 1218 ids = ", ".join( [ str(x) for x in idList]) 1219 cmd = """select l.id_org, number, street, city, postcode, state, country 1220 from dem.v_basic_address v , dem.lnk_org2address l 1221 where v.addr_id = l.id_address and 1222 l.id_org in ( select id from dem.org where id in (%s) ) """ % ids 1223 result = gmPG.run_ro_query( "personalia", cmd) 1224 1225 if result == None: 1226 _log.error("failure in org address load" ) 1227 return None 1228 m = {} 1229 for (id_org, n,s,ci,p,st,co) in result: 1230 m[id_org] = (n,s,ci,p,st,co) 1231 return m
1232
1233 -def get_org_data_for_org_ids(idList):
1234 """ for a given list of org id values , 1235 returns a map of id_org vs. org attributes: description, id_category""" 1236 1237 ids = ", ".join( [ str(x) for x in idList]) 1238 cmd = """select id, description, id_category from dem.org 1239 where id in ( select id from dem.org where id in( %s) )""" % ids 1240 #<DEBUG> 1241 print cmd 1242 #</DEBUG> 1243 result = gmPG.run_ro_query("personalia", cmd, ) 1244 if result is None: 1245 _log.error("Unable to load orgs with ids (%s)" %ids) 1246 return None 1247 m = {} 1248 for (id_org, d, id_cat) in result: 1249 m[id_org] = (d, id_cat) 1250 return m
1251 1252 1253 1254 1255 #============================================================ 1256 # 1257 # IGNORE THE FOLLOWING, IF NOT INTERESTED IN TEST CODE 1258 # 1259 # 1260 1261 if __name__ == '__main__': 1262 print "Please enter a write-enabled user e.g. _test-doc " 1263
1264 - def testListOrgs():
1265 print "running test listOrg" 1266 for (f,a) in get_test_data(): 1267 h = cOrgImpl1() 1268 h.set(*f) 1269 h.setAddress(*a) 1270 if not h.save(): 1271 print "did not save ", f 1272 1273 orgs = cOrgHelperImpl1().findAllOrganizations() 1274 1275 for org in orgs: 1276 print "Found org ", org.get(), org.getAddress() 1277 if not org.shallow_del(): 1278 print "Unable to delete above org"
1279 1280 1281 1282 1283 1284
1285 - def get_test_data():
1286 """test org data for unit testing in testOrg()""" 1287 return [ 1288 ( ["Box Hill Hospital", "", "", "Eastern", "hospital", "0398953333", "111-1111","bhh@oz", ""], ["33", "Nelson Rd", "Box Hill", "3128", None , None] ), 1289 ( ["Frankston Hospital", "", "", "Peninsula", "hospital", "0397847777", "03784-3111","fh@oz", ""], ["21", "Hastings Rd", "Frankston", "3199", None , None] ) 1290 ]
1291
1292 - def get_test_persons():
1293 return { "Box Hill Hospital": 1294 [ 1295 ['Dr.', 'Bill' , 'Smith', '123-4567', '0417 111 222'], 1296 ['Ms.', 'Anita', 'Jones', '124-5544', '0413 222 444'], 1297 ['Dr.', 'Will', 'Stryker', '999-4444', '0402 333 111'] ], 1298 "Frankston Hospital": 1299 [ [ "Dr.", "Jason", "Boathead", "444-5555", "0403 444 2222" ], 1300 [ "Mr.", "Barnie", "Commuter", "222-1111", "0444 999 3333"], 1301 [ "Ms.", "Morita", "Traveller", "999-1111", "0222 333 1111"]] }
1302
1303 - def testOrgPersons():
1304 m = get_test_persons() 1305 d = dict( [ (f[0] , (f, a)) for (f, a) in get_test_data() ] ) 1306 for orgName , personList in m.items(): 1307 f1 , a1 = d[orgName][0], d[orgName][1] 1308 _testOrgClassPersonRun( f1, a1, personList, cOrgImpl1) 1309 _testOrgClassPersonRun( f1, a1, personList, cCompositeOrgImpl1) 1310 _testOrgClassPersonRun( f1, a1, personList, cOrgHelperImpl1().create) 1311 _testOrgClassPersonRun( f1, a1, personList, cOrgHelperImpl2().create) 1312 _testOrgClassPersonRun( f1, a1, personList, cOrgHelperImpl3().create) 1313 _testOrgClassPersonRun( f1, a1, personList, cOrgHelperImpl3().create, getTestIdentityUsing_cOrgDemographicAdapter)
1314 1315 1316
1317 - def _outputPersons( org):
1318 m = org.getPersonMap() 1319 1320 if m== []: 1321 print "NO persons were found unfortunately" 1322 1323 print """ TestOrgPersonRun got back for """ 1324 a = org.getAddress() 1325 print org["name"], a["number"], a["street"], a["urb"], a["postcode"] , " phone=", org['phone'] 1326 1327 for id, r in m.items(): 1328 print "\t",", ".join( [ " ".join(r.get_names().values()), 1329 "work no=", r.getCommChannel(gmDemographicRecord.WORK_PHONE), 1330 "mobile no=", r.getCommChannel(gmDemographicRecord.MOBILE) 1331 ] )
1332 1333
1334 - def _testOrgPersonRun(f1, a1, personList):
1335 print "Using test data :f1 = ", f1, "and a1 = ", a1 , " and lp = ", personList 1336 print "-" * 50 1337 _testOrgClassPersonRun( f1, a1, personList, cOrgImpl1) 1338 _testOrgClassPersonRun( f1, a1, personList, cCompositeOrgImpl1) 1339 _testOrgClassPersonRun( f1, a1, personList, cOrgHelperImpl1().create) 1340 _testOrgClassPersonRun( f1, a1, personList, cOrgHelperImpl2().create)
1341 1342
1343 - def _setIdentityTestData(identity, data):
1344 identity.addName(data[1], data[2], True) 1345 identity.setTitle(data[0]) 1346 identity.linkCommChannel( gmDemographicRecord.WORK_PHONE, data[3]) 1347 identity.linkCommChannel( gmDemographicRecord.MOBILE, data[4])
1348
1349 - def getTestIdentityUsingDirectDemographicRecord( data, org):
1350 id = gmPerson.create_dummy_identity() 1351 identity = gmDemographicRecord.cDemographicRecord_SQL(id) 1352 _setIdentityTestData(identity, data) 1353 return identity
1354
1355 - def getTestIdentityUsing_cOrgDemographicAdapter( data, org):
1356 helper = cOrgHelperImpl3() 1357 orgPerson= helper.createOrgPerson() 1358 orgPerson.setParent(org) 1359 orgPerson['name'] = ' '.join( [data[0], data[1], data[2]]) 1360 orgPerson['phone'] = data[3] 1361 orgPerson['mobile'] = data[4] 1362 orgPerson.save() 1363 return orgPerson.getDemographicRecord()
1364 1365
1366 - def _testOrgClassPersonRun(f1, a1, personList, orgCreate, identityCreator = getTestIdentityUsingDirectDemographicRecord):
1367 print "-" * 50 1368 print "Testing org creator ", orgCreate 1369 print " and identity creator ", identityCreator 1370 print "-" * 50 1371 h = orgCreate() 1372 h.set(*f1) 1373 h.setAddress(*a1) 1374 if not h.save(): 1375 print "Unable to save org for person test" 1376 h.shallow_del() 1377 return False 1378 # use gmDemographicRecord to convert person list 1379 for lp in personList: 1380 identity = identityCreator(lp, h) 1381 result , msg = h.linkPerson(identity) 1382 print msg 1383 1384 _outputPersons(h) 1385 deletePersons(h) 1386 1387 if h.shallow_del(): 1388 print "Managed to dispose of org" 1389 else: 1390 print "unable to dispose of org" 1391 1392 return True
1393 1394 # def testOrgPerson(f1, a1, personList): 1395
1396 - def deletePerson(id):
1397 cmds = [ ( "delete from dem.lnk_identity2comm_chan where fk_identity=%d"%id,[]), 1398 ("delete from dem.names where id_identity=%d"%id,[]), 1399 ("delete from dem.identity where id = %d"%id,[]) ] 1400 result = gmPG.run_commit("personalia", cmds) 1401 return result
1402
1403 - def deletePersons( org):
1404 map = org.getPersonMap() 1405 for id, r in map.items(): 1406 org.unlinkPerson(r) 1407 1408 result = deletePerson(r.getID()) 1409 if result == None: 1410 _log.error("FAILED TO CLEANUP PERSON %d" %r.getID() )
1411 1412 1413
1414 - def testOrg():
1415 """runs a test of load, save , shallow_del on items in from get_test_data""" 1416 l = get_test_data() 1417 results = [] 1418 for (f, a) in l: 1419 result, obj = _testOrgRun(f, a) 1420 results.append( (result, obj) ) 1421 return results
1422 1423 1424
1425 - def _testOrgRun( f1, a1):
1426 1427 print """testing single level orgs""" 1428 f = [ "name", "office", "subtype", "memo", "category", "phone", "fax", "email","mobile"] 1429 a = ["number", "street", "urb", "postcode", "state", "country"] 1430 h = cOrgImpl1() 1431 1432 h.set(*f1) 1433 h.setAddress(*a1) 1434 1435 print "testing get, getAddress" 1436 print h.get() 1437 print h.getAddressDict() 1438 1439 import sys 1440 if not h.save(): 1441 print "failed to save first time. Is an old test org needing manual removal?" 1442 return False, h 1443 print "saved pk =", h.getId() 1444 1445 1446 pk = h.getId() 1447 if h.shallow_del(): 1448 print "shallow deleted ", h['name'] 1449 else: 1450 print "failed shallow delete of ", h['name'] 1451 1452 1453 1454 h2 = cOrgImpl1() 1455 1456 print "testing load" 1457 1458 print "should fail" 1459 if not h2.load(pk): 1460 print "Failed as expected" 1461 1462 if h.save(): 1463 print "saved ", h['name'] , "again" 1464 else: 1465 print "failed re-save" 1466 return False, h 1467 1468 h['fax'] = '222-1111' 1469 print "using update save" 1470 1471 if h.save(): 1472 print "saved updated passed" 1473 print "Test reload next" 1474 else: 1475 print "failed save of updated data" 1476 print "continuing to reload" 1477 1478 1479 if not h2.load(h.getId()): 1480 print "failed load" 1481 return False, h 1482 print "reloaded values" 1483 print h2.get() 1484 print h2.getAddressDict() 1485 1486 print "** End of Test org" 1487 1488 if h2.shallow_del(): 1489 print "cleaned up" 1490 else: 1491 print "Test org needs to be manually removed" 1492 1493 return True, h2
1494
1495 - def clean_test_org():
1496 l = get_test_data() 1497 1498 names = [ "".join( ["'" ,str(org[0]), "'"] ) for ( org, address) in l] 1499 names += [ "'John Hunter Hospital'", "'Belmont District Hospital'"] 1500 nameList = ",".join(names) 1501 categoryList = "'hospital'" 1502 1503 cmds = [ ( """create temp table del_org as 1504 select id from dem.org 1505 where description in(%s) or 1506 id_category in ( select id from dem.org_category c 1507 where c.description in (%s)) 1508 """ % (nameList, categoryList), [] ), 1509 ("""create temp table del_identity as 1510 select id from dem.identity 1511 where id in 1512 ( 1513 select id_identity from dem.lnk_person_org_address 1514 where id_org in ( select id from del_org) 1515 )""",[] ), 1516 ("""create temp table del_comm as 1517 (select id_comm from dem.lnk_org2comm_channel where 1518 id_org in ( select id from del_org) 1519 ) UNION 1520 (select id_comm from dem.lnk_identity2comm_chan where 1521 id_identity in ( select id from del_identity) 1522 )""", [] ), 1523 ("""delete from dem.names where id_identity in 1524 (select id from del_identity)""",[]), 1525 ("""delete from dem.lnk_person_org_address where 1526 id_org in (select id from del_org )""",[]), 1527 ("""delete from dem.lnk_person_org_address where 1528 id_identity in (select id from del_identity)""", []), 1529 ("""delete from dem.lnk_org2comm_channel 1530 where id_org in (select id from del_org) """,[]), 1531 ("""delete from dem.lnk_identity2comm_chan 1532 where id_identity in (select id from del_identity)""",[] ), 1533 ("""delete from dem.comm_channel where id in ( select id_comm from del_comm)""",[]), 1534 ("""delete from dem.lnk_job2person where id_identity in (select id from del_identity)""", []), 1535 ("""delete from dem.identity where id in (select id from del_identity)""",[] ), 1536 ("""delete from dem.org where id in ( select id from del_org) """ , [] ), 1537 ("""drop table del_comm""",[]), 1538 ("""drop table del_identity""",[]), 1539 ("""drop table del_org""", []) 1540 1541 ] 1542 result = gmPG.run_commit("personalia", cmds) <> None 1543 1544 return result
1545 1546
1547 - def login_user_and_test(logintest, service = 'personalia', msg = "failed test" , use_prefix_rw= False):
1548 """ tries to get and verify a read-write connection 1549 which has permission to write to org tables, so the test case 1550 can run. 1551 """ 1552 login2 = gmPG.request_login_params() 1553 1554 #login as the RW user 1555 p = gmPG.ConnectionPool( login2) 1556 if use_prefix_rw: 1557 conn = p.GetConnection( service, readonly = 0) 1558 else: 1559 conn = p.GetConnection(service) 1560 result = logintest(conn) 1561 1562 if result is False: 1563 print msg 1564 1565 p.ReleaseConnection(service) 1566 return result, login2
1567
1568 - def test_rw_user(conn):
1569 # test it is a RW user, by making a entry and deleting it 1570 try: 1571 c.reload("org_category") 1572 cursor = conn.cursor() 1573 1574 cursor.execute("select last_value from dem.org_id_seq") 1575 [org_id_seq] = cursor.fetchone() 1576 1577 cursor.execute(""" 1578 insert into dem.org ( description, id_category, id) 1579 values ( 'xxxDEFAULTxxx', %d, 1580 %d) 1581 """ % ( c.getId('org_category', 'hospital') , org_id_seq + 1 ) ) 1582 cursor.execute(""" 1583 delete from dem.org where id = %d""" % ( org_id_seq + 1) ) 1584 # make sure this exercise is committed, else a deadlock will occur 1585 conn.commit() 1586 except: 1587 _log.exception("Test of Update Permission failed") 1588 return False 1589 return True
1590
1591 - def test_admin_user(conn):
1592 try: 1593 cursor = conn.cursor() 1594 1595 cursor.execute("select last_value from dem.org_category_id_seq") 1596 [org_cat_id_seq] = cursor.fetchone() 1597 1598 cursor.execute(""" 1599 insert into dem.org_category ( description, id) 1600 values ( 'xxxDEFAULTxxx',%d) 1601 """ % (org_cat_id_seq + 1 ) ) 1602 cursor.execute(""" 1603 delete from dem.org_category where description like 'xxxDEFAULTxxx' """ ) 1604 # make sure this exercise is committed, else a deadlock will occur 1605 conn.commit() 1606 except: 1607 _log.exception("Test of Update Permission failed") 1608 return False 1609 return True
1610
1611 - def login_rw_user():
1612 return login_user_and_test( test_rw_user, "login cannot update org", use_prefix_rw = True)
1613 1614
1615 - def login_admin_user():
1616 return login_user_and_test( test_admin_user, "login cannot update org_category" )
1617 1618
1619 - def create_temp_categories( categories = ['hospital']):
1620 print "NEED TO CREATE TEMPORARY ORG_CATEGORY.\n\n ** PLEASE ENTER administrator login : e.g user 'gm-dbo' and his password" 1621 #get a admin login 1622 for i in xrange(0, 4): 1623 result ,tmplogin = login_admin_user() 1624 if result: 1625 break 1626 if i == 4: 1627 print "Failed to login" 1628 return categories 1629 1630 # and save it , for later removal of test categories. 1631 from Gnumed.pycommon import gmLoginInfo 1632 adminlogin = gmLoginInfo.LoginInfo(*tmplogin.GetInfo()) 1633 1634 #login as admin 1635 p = gmPG.ConnectionPool( tmplogin) 1636 conn = p.GetConnection("personalia") 1637 1638 # use the last value + 1 of the relevant sequence, but don't increment it 1639 cursor = conn.cursor() 1640 1641 failed_categories = [] 1642 n =1 1643 for cat in categories: 1644 cursor.execute("select last_value from dem.org_category_id_seq") 1645 [org_cat_id_seq] = cursor.fetchone() 1646 1647 cursor.execute( "insert into dem.org_category(description, id) values('%s', %d)" % (cat, org_cat_id_seq + n) ) 1648 cursor.execute("select id from dem.org_category where description in ('%s')" % cat) 1649 1650 result = cursor.fetchone() 1651 if result == None or len(result) == 0: 1652 failed_categories.append(cat) 1653 print "Failed insert of category", cat 1654 conn.rollback() 1655 else: 1656 conn.commit() 1657 n += 1 1658 1659 conn.commit() 1660 p.ReleaseConnection('personalia') 1661 return failed_categories, adminlogin
1662
1663 - def clean_org_categories(adminlogin = None, categories = ['hospital'], service='personalia'):
1664 1665 print""" 1666 1667 The temporary category(s) will now 1668 need to be removed under an administrator login 1669 e.g. gm-dbo 1670 Please enter login for administrator: 1671 """ 1672 if adminlogin is None: 1673 for i in xrange(0, 4): 1674 result, adminlogin = login_admin_user() 1675 if result: 1676 break 1677 if i == 4: 1678 print "FAILED TO LOGIN" 1679 return categories 1680 1681 p = gmPG.ConnectionPool(adminlogin) 1682 conn = p.GetConnection(service) 1683 failed_remove = [] 1684 for cat in categories: 1685 try: 1686 cursor = conn.cursor() 1687 cursor.execute( "delete from dem.org_category where description in ('%s')"%cat) 1688 conn.commit() 1689 cursor.execute("select id from dem.org_category where description in ('%s')"%cat) 1690 if cursor.fetchone() == None: 1691 print "Succeeded in removing temporary org_category" 1692 else: 1693 print "*** Unable to remove temporary org_category" 1694 failed_remove .append(cat) 1695 except: 1696 import sys 1697 print sys.exc_info()[0], sys.exc_info()[1] 1698 import traceback 1699 traceback.print_tb(sys.exc_info()[2]) 1700 1701 failed_remove.append(cat) 1702 1703 conn = None 1704 p.ReleaseConnection(service) 1705 if failed_remove <> []: 1706 print "FAILED TO REMOVE ", failed_remove 1707 return failed_remove
1708
1709 - def test_CatFinder():
1710 print "TESTING cCatFinder" 1711 1712 print """c = cCatFinder("org_category")""" 1713 c = cCatFinder("org_category") 1714 1715 print c.getCategories("org_category") 1716 1717 print """c = cCatFinder("enum_comm_types")""" 1718 c = cCatFinder("enum_comm_types") 1719 1720 l = c.getCategories("enum_comm_types") 1721 print "testing getId()" 1722 l2 = [] 1723 for x in l: 1724 l2.append((x, c.getId("enum_comm_types", x))) 1725 print l2 1726 1727 print """testing borg behaviour of cCatFinder""" 1728 1729 print c.getCategories("org_category")
1730 1731
1732 - def help():
1733 print """\nNB If imports not found , try: 1734 1735 change to gnumed/client directory , then 1736 1737 export PYTHONPATH=$PYTHONPATH:../;python business/gmOrganization.py 1738 1739 --clean , cleans the test data and categories 1740 1741 --gui sets up as for no arguments, then runs the client. 1742 on normal exit of client, normal tests run, and 1743 then cleanup of entered data. 1744 1745 using the gui, 1746 1747 the 'list organisations' toolbar button , loads all organisations 1748 in the database, and display suborgs and persons associated 1749 with each organisation. 1750 1751 the 'add organisation' button will add a top-level organisation. 1752 the 'add branch/division' button will work when the last selected 1753 org was a top level org. 1754 1755 the 'add person M|F' button works if an org is selected. 1756 1757 the save button works when entry is finished. 1758 1759 selecting on an item, will bring it into the editing area. 1760 1761 No test yet for dirtied edit data, to query whether to 1762 save or discard. (30/5/2004) 1763 """ 1764 print 1765 print "In the connection query, please enter" 1766 print "a WRITE-ENABLED user e.g. _test-doc (not test-doc), and the right password" 1767 print 1768 print "Run the unit test with cmdline argument '--clean' if trying to clean out test data" 1769 print 1770 1771 print """You can get a sermon by running 1772 export PYTHONPATH=$PYTHONPATH:../;python business/gmOrganization.py --sermon 1773 """ 1774 print """ 1775 Pre-requisite data in database is : 1776 gnumed=# select * from org_category ; 1777 id | description 1778 ----+------------- 1779 1 | hospital 1780 (1 row) 1781 1782 gnumed=# select * from enum_comm_types ; 1783 id | description 1784 ----+------------- 1785 1 | email 1786 2 | fax 1787 3 | homephone 1788 4 | workphone 1789 5 | mobile 1790 6 | web 1791 7 | jabber 1792 (7 rows) 1793 """
1794
1795 - def sermon():
1796 print""" 1797 This test case shows how many things can go wrong , even with just a test case. 1798 Problem areas include: 1799 - postgres administration : pg_ctl state, pg_hba.conf, postgres.conf config files . 1800 - schema integrity constraints : deletion of table entries which are subject to foreign keys, no input for no default value and no null value columns, input with duplicated values where unique key constraint applies to non-primary key columns, dealing with access control by connection identity management. 1801 1802 1803 - efficiency trade-offs -e.g. using db objects for localising code with data and easier function call interface ( then hopefully, easier to program with) , vs. need to access many objects at once 1804 without calling the backend for each object. 1805 1806 - error and exception handling - at what point in the call stack to handle an error. 1807 Better to use error return values and log exceptions near where they occur, vs. wrapping inside try: except: blocks and catching typed exceptions. 1808 1809 1810 - test-case construction: test data is needed often, and the issue 1811 is whether it is better to keep the test data volatile in the test-case, 1812 which handles both its creation and deletion, or to add it to test data 1813 server configuration files, which may involve running backend scripts 1814 for loading and removing test data. 1815 1816 1817 1818 - Database connection problems: 1819 -Is the problem in : 1820 - pg_ctl start -D /...mydata-directory is wrong, and gnumed isn't existing there. 1821 1822 - ..mydata-directory/pg_hba.conf 1823 - can psql connect locally and remotely with the username and password. 1824 - Am I using md5 authenentication and I've forgotten the password. 1825 - I need to su postgres, alter pg_hba.conf to use trust for 1826 the gnumed database, pg_ctl restart -D .., su normal_user, psql gnumed, alter user my_username password 'doh' 1827 - might be helpful: the default password for _test-doc is test-doc 1828 1829 - ../mydata-directory/postgres.conf 1830 - tcp connect flag isn't set to true 1831 1832 - remote/local mixup : 1833 a different set of user passwords on different hosts. e.g the password 1834 for _test-doc is 'pass' on localhost and 'test-doc' for the serverhost. 1835 - In the prompts for admin and user login, local host was used for one, and 1836 remote host for the other 1837 1838 1839 1840 - test data won't go away : 1841 - 'hospital' category in org_category : the test case failed in a previous run 1842 and the test data was left there; now the test case won't try to delete it 1843 because it exists as a pre-existing category; 1844 soln : run with --clean option 1845 1846 1847 - test-case failed unexpectedly, or break key was hit in the middle of a test-case run. 1848 Soln: run with --clean option, 1849 1850 1851 """
1852 1853 1854 #============================================================ 1855 1856 import sys 1857 testgui = False 1858 if len(sys.argv) > 1: 1859 if sys.argv[1] == '--clean': 1860 result = clean_test_org() 1861 p = gmPG.ConnectionPool() 1862 p.ReleaseConnection('personalia') 1863 if result: 1864 print "probably succeeded in cleaning orgs" 1865 else: print "failed to clean orgs" 1866 1867 clean_org_categories() 1868 sys.exit(1) 1869 1870 if sys.argv[1] == "--sermon": 1871 sermon() 1872 1873 if sys.argv[1] == "--help": 1874 help() 1875 1876 if sys.argv[1] =="--gui": 1877 testgui = True 1878 1879 print "*" * 50 1880 print "RUNNING UNIT TEST of gmOrganization " 1881 1882 1883 test_CatFinder() 1884 tmp_category = False # tmp_category means test data will need to be added and removed 1885 # for org_category . 1886 1887 c = cCatFinder() 1888 if not "hospital" in c.getCategories("org_category") : 1889 print "FAILED in prerequisite for org_category : test categories are not present." 1890 1891 tmp_category = True 1892 1893 if tmp_category: 1894 # test data in a categorical table (restricted access) is needed 1895 1896 print """You will need to switch login identity to database administrator in order 1897 to have permission to write to the org_category table, 1898 and then switch back to the ordinary write-enabled user in order 1899 to run the test cases. 1900 Finally you will need to switch back to administrator login to 1901 remove the temporary org_categories. 1902 """ 1903 categories = ['hospital'] 1904 result, adminlogin = create_temp_categories(categories) 1905 if result == categories: 1906 print "Unable to create temporary org_category. Test aborted" 1907 sys.exit(-1) 1908 if result <> []: 1909 print "UNABLE TO CREATE THESE CATEGORIES" 1910 if not raw_input("Continue ?") in ['y', 'Y'] : 1911 sys.exit(-1) 1912 1913 try: 1914 results = [] 1915 if tmp_category: 1916 print "succeeded in creating temporary org_category" 1917 print 1918 print "** Now ** RESUME LOGIN ** of write-enabled user (e.g. _test-doc) " 1919 while (1): 1920 # get the RW user for org tables (again) 1921 if login_rw_user(): 1922 break 1923 1924 if testgui: 1925 if cCatFinder().getId('org_category','hospital') == None: 1926 print "Needed to set up temporary org_category 'hospital" 1927 sys.exit(-1) 1928 import os 1929 print os.environ['PWD'] 1930 os.spawnl(os.P_WAIT, "/usr/bin/python", "/usr/bin/python","wxpython/gnumed.py", "--debug") 1931 1932 #os.popen2('python client/wxpython/gnumed.py --debug') 1933 1934 # run the test case 1935 results = testOrg() 1936 1937 # cleanup after the test case 1938 for (result , org) in results: 1939 if not result and org.getId() <> None: 1940 print "trying cleanup" 1941 if org.shallow_del(): print " may have succeeded" 1942 else: 1943 print "May need manual removal of org id =", org.getId() 1944 1945 testOrgPersons() 1946 1947 testListOrgs() 1948 1949 except: 1950 import sys 1951 print sys.exc_info()[0], sys.exc_info()[1] 1952 _log.exception( "Fatal exception") 1953 1954 # clean-up any temporary categories. 1955 if tmp_category: 1956 try: 1957 clean_org_categories(adminlogin) 1958 except: 1959 while(not login_rw_user()[0]): 1960 pass 1961 clean_test_org() 1962 clean_org_categories(adminlogin) 1963 1964 1965
1966 -def setPostcodeWidgetFromUrbId(postcodeWidget, id_urb):
1967 """convenience method for urb and postcode phrasewheel interaction. 1968 never called without both arguments, but need to check that id_urb 1969 is not invalid""" 1970 #TODO type checking that the postcodeWidget is a phrasewheel configured 1971 # with a postcode matcher 1972 if postcodeWidget is None or id_urb is None: 1973 return False 1974 postcode = getPostcodeForUrbId(id_urb) 1975 if postcode is None: 1976 return False 1977 if len(postcode) == 0: 1978 return True 1979 postcodeWidget.SetValue(postcode) 1980 postcodeWidget.input_was_selected= 1 1981 return True
1982 1983 #------------------------------------------------------------ 1984
1985 -def setUrbPhraseWheelFromPostcode(pwheel, postcode):
1986 """convenience method for common postcode to urb phrasewheel collaboration. 1987 there is no default args for these utility functions, 1988 This function is never called without both arguments, otherwise 1989 there is no intention (= modify the urb phrasewheel with postcode value). 1990 """ 1991 # TODO type checking that the pwheel is a urb phrasewheel with a urb matcher 1992 # clearing post code unsets target 1993 # phrasewheel's postcode context 1994 if pwheel is None: 1995 return False 1996 if postcode == '': 1997 pwheel.set_context("postcode", "%") 1998 return True 1999 urbs = getUrbsForPostcode(postcode) 2000 if urbs is None: 2001 return False 2002 if len(urbs) == 0: 2003 return True 2004 pwheel.SetValue(urbs[0]) 2005 pwheel.input_was_selected = 1 2006 2007 # FIXME: once the postcode context is set, 2008 # the urb phrasewheel will only return urbs with 2009 # the same postcode. These can be viewed by clearing 2010 # the urb widget. ?How to unset the postcode context, 2011 # some gui gesture ? clearing the postcode 2012 # (To view all the urbs for a set context, 2013 # put a "*" in the urb box and activate the picklist. 2014 # THE PROBLEM WITH THIS IS IF THE USER CLEARS THE BOX AND SET CONTEXT IS RESET, 2015 # then the "*" will try to pull all thousands of urb names, freezing the app. 2016 # so needs a fixup (? have SQL select ... LIMIT n in Phrasewheel ) 2017 2018 pwheel.set_context("postcode", postcode) 2019 return True
2020 2021 #=========================================================== 2022 # $Log: gmOrganization.py,v $ 2023 # Revision 1.40 2009/10/21 08:55:41 ncq 2024 # - cleanup 2025 # 2026 # Revision 1.39 2008/01/30 13:34:50 ncq 2027 # - switch to std lib logging 2028 # 2029 # Revision 1.38 2008/01/11 16:08:07 ncq 2030 # - first/last -> first-/lastnames 2031 # 2032 # Revision 1.37 2007/12/02 20:56:37 ncq 2033 # - adjust to table changes 2034 # 2035 # Revision 1.36 2007/07/17 10:38:06 ncq 2036 # - fix some epydoc related stuff 2037 # 2038 # Revision 1.35 2006/07/19 20:25:00 ncq 2039 # - gmPyCompat.py is history 2040 # 2041 # Revision 1.34 2006/01/07 17:40:56 ncq 2042 # - lots of schema qualification 2043 # 2044 # Revision 1.33 2005/06/07 10:15:47 ncq 2045 # - setContext -> set_context 2046 # 2047 # Revision 1.32 2005/01/31 10:37:26 ncq 2048 # - gmPatient.py -> gmPerson.py 2049 # 2050 # Revision 1.31 2005/01/12 14:47:48 ncq 2051 # - in DB speak the database owner is customarily called dbo, hence use that 2052 # 2053 # Revision 1.30 2004/06/21 16:01:55 ncq 2054 # - cleanup, trying to make epydoc fix do the right thing 2055 # 2056 # Revision 1.29 2004/06/21 15:08:45 sjtan 2057 # 2058 # fixup for epydoc, remove global module scope database access. 2059 # 2060 # Revision 1.28 2004/06/21 14:48:26 sjtan 2061 # 2062 # restored some methods that gmContacts depends on, after they were booted 2063 # out from gmDemographicRecord with no home to go , works again ; 2064 # removed cCatFinder('occupation') instantiating in main module scope 2065 # which was a source of complaint , as it still will lazy load anyway. 2066 # 2067 # Revision 1.27 2004/06/01 15:11:56 sjtan 2068 # 2069 # cut ctrl-x and paste ctrl-v, works through clipboard, so can paste name/address info onto 2070 # text editors (oowriter, kwrite tried out). Drag and drop doesn't work to outside apps. 2071 # List displays at last updated position after load_all_orgs() called. removed 2072 # old display data listing on list org display button press, because cutting and pasting 2073 # persons to these items loses persons. Only saves top-level orgs if there is a valid 2074 # category value in category field. 2075 # 2076 # Revision 1.26 2004/06/01 07:15:05 ncq 2077 # - made cPerson into "private" class _cPersonMarker (as per the comment) 2078 # such that never ever even the slighest confusion will arise whether to 2079 # use that "class" or the cPerson in gmPatient.py 2080 # 2081 # Revision 1.25 2004/05/31 14:24:19 sjtan 2082 # 2083 # intra-list cut and paste implemented. Not using wxClipboard ( could paste textified person 2084 # into clipboard ). Now the GP can be moved out of the Engineering department , but he may not be happy ;) 2085 # 2086 # Revision 1.24 2004/05/30 13:02:49 sjtan 2087 # 2088 # test help; need drag and drop to correct erroneous person-org relationships. 2089 # 2090 # Revision 1.23 2004/05/30 11:08:17 sjtan 2091 # 2092 # fixup clean_test_org to include delete from lnk_job2person ... 2093 # 2094 # Revision 1.22 2004/05/30 03:50:41 sjtan 2095 # 2096 # gmContacts can create/update org, one level of sub-org, org persons, sub-org persons. 2097 # pre-alpha or alpha ? Needs cache tune-up . 2098 # 2099 # Revision 1.21 2004/05/29 08:22:07 sjtan 2100 # 2101 # indented to put all test code in __name__=__main__ block. 2102 # 2103 # Revision 1.20 2004/05/28 15:13:53 sjtan 2104 # 2105 # one level of sub orgs done ; can enter departments; category stays as hospital. 2106 # 2107 # Revision 1.19 2004/05/28 13:19:23 ncq 2108 # - Syan, can't we move all test related code 2109 # into if __name__ == '__main__' ? 2110 # 2111 # Revision 1.18 2004/05/28 04:29:53 sjtan 2112 # 2113 # gui test case option; should setup/teardown ok if correct logins. 2114 # 2115 # Revision 1.17 2004/05/28 01:20:14 sjtan 2116 # 2117 # cleanup script would probably work for comm_channel if left out org.del_shallow() 2118 # in test runs. 2119 # 2120 # Revision 1.16 2004/05/26 18:21:38 sjtan 2121 # 2122 # add org , save toolbar buttons linked, list select linked, needs testing, 2123 # must have 'hospital' if table org_category. 2124 # 2125 # Revision 1.15 2004/05/25 14:10:45 sjtan 2126 # 2127 # cleanup temp persons as well. 2128 # 2129 # Revision 1.14 2004/05/25 13:32:45 sjtan 2130 # 2131 # cleanup, obsolete removed. 2132 # 2133 # Revision 1.13 2004/05/24 21:09:01 ncq 2134 # - cleanup 2135 # 2136 # Revision 1.12 2004/05/24 05:49:59 sjtan 2137 # 2138 # test case working for gmDemographicRecord_SQL linking/unlinking; local and remote tested. 2139 # 2140 # Revision 1.11 2004/05/24 03:34:56 sjtan 2141 # 2142 # tested local and remote test case; setup/pulldown for test case is within test case. 2143 # 2144 # Revision 1.10 2004/05/24 00:32:24 sjtan 2145 # 2146 # don't want to increment the sequence number for a temporary org_category, as there is no way 2147 # of restoring it. 2148 # 2149 # Revision 1.9 2004/05/23 15:27:56 sjtan 2150 # 2151 # allow test case to run without sql test data script for org tables. 2152 # 2153 # Revision 1.8 2004/05/23 15:22:41 sjtan 2154 # 2155 # allow Unit testcase to run in naive database, by allowing temporary org_category creation/deletion. 2156 # 2157 # Revision 1.7 2004/05/23 13:27:51 sjtan 2158 # 2159 # refactored so getting n orgs using a finder operation will make 3 sql calls 2160 # instead of n x 3 calls (reduce network traffic). Test case expanded, and 2161 # cleanup option for running unit test case added. 2162 # 2163 # Revision 1.6 2004/05/23 11:26:19 sjtan 2164 # 2165 # getPk() now getId() , more consistent with other modules. 2166 # 2167 # Revision 1.5 2004/05/22 10:31:29 ncq 2168 # - == None -> is None 2169 # 2170 # Revision 1.4 2004/05/21 15:39:22 sjtan 2171 # 2172 # passed unit test for save, load, shallow_del, save again, update and save. 2173 # 2174 # Revision 1.3 2004/05/20 15:37:12 sjtan 2175 # 2176 # pre-test version of gmOrganization connecting to current org tables. Needs 2177 # unit testing, and then handling of subOrgs and organizational people 2178 # linking. Some cut and paste of linkAddress from gmDemographicRecord. Not 2179 # for use . 2180 # 2181 # Revision 1.2 2004/05/16 13:05:14 ncq 2182 # - remove methods that violate the basic rules for 2183 # clinical items (eg no creation via clin item objects) 2184 # - cleanup 2185 # 2186