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

Source Code for Module Gnumed.business.gmOrganization

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