def process_line(infile): """ Scan all lines in INFILE and create corresponding account/e-mail entries in Cerebrum. """ stream = open(infile, 'r') # Iterate over all persons: for line in stream: logger.debug5("Processing line: |%s|", line) fields = string.split(line.strip(), ";") print len(fields) if len(fields) != 5: logger.error("Bad line: %s. Skipping" % line) continue # fi fnr, pname, uname, email, foo = fields if not fnr == "": logger.debug("Processing person %s", fnr) try: fodselsnr.personnr_ok(fnr) except fodselsnr.InvalidFnrError: logger.warn("Bad no_ssn %s, skipping", fnr) continue person.clear() gender = constants.gender_male if fodselsnr.er_kvinne(fnr): gender = constants.gender_female y, m, d = fodselsnr.fodt_dato(fnr) # Can't just populate like this, we need to search for persons # first. try: person.find_by_external_id(constants.externalid_fodselsnr, fnr) except Errors.NotFoundError: pass person.populate(db.Date(y, m, d), gender) person.affect_external_id(constants.system_ekstens, constants.externalid_fodselsnr) person.populate_external_id(constants.system_ekstens, constants.externalid_fodselsnr, fnr) person.write_db() update_names(fnr, pname) person.write_db() logger.debug("Created new person with fnr %s", fnr) p_id = person.entity_id account_id = process_user(p_id, uname) process_mail(account_id, 'defaultmail', email)
def p_create(fnr, lname, fname): gender = constants.gender_female try: fodselsnr.personnr_ok(fnr) except fodselsnr.InvalidFnrError: logger.error("Cannot create person, bad no_ssn |%s|", fnr) return None if not (lname and fname): logger.error("Cannot create person %s, missing name") return None if fodselsnr.er_mann(fnr): gender = constants.gender_female # this is very wrong, but as we do not have any birth days # registered we need to try producing them. year, mon, day = fodselsnr.fodt_dato(fnr) # in order to be a guardian you need to be over 18 years old which # excludes anyone born in after 1990 so this will work for now :-) birth_date = db.Date(year, mon, day) person.clear() # Create person person.populate(birth_date, gender) person.affect_external_id(constants.system_manual, constants.externalid_fodselsnr) person.populate_external_id(constants.system_manual, constants.externalid_fodselsnr, fnr) person.write_db() logger.info("Created new person with fnr %s", fnr) # Add person_name person.affect_names(constants.system_manual, constants.name_first, constants.name_last) person.populate_name(constants.name_first, fname) person.populate_name(constants.name_last, lname) person.write_db() logger.debug("Added name %s %s to fnr %s", fname, lname, fnr) return person.entity_id
def populate_external_ids(tpl): """ Locate (or create) a person holding the IDs contained in FIELDS and register these external IDs if necessary. This function both alters the PERSON object and retuns a boolean value (True means the ID update/lookup was successful, False means the update/lookup failed and nothing can be ascertained about the PERSON's state). There are two external IDs in SAP -- the Norwegian social security number (11-siffret personnummer, fnr) and the SAP employee id (sap_id). SAP IDs are (allegedly) permanent and unique, fnr can change. """ error, person = locate_person(tpl.sap_ansattnr, tpl.sap_fnr) if error: logger.error("Lookup for (sap_id; fnr) == (%s; %s) failed", tpl.sap_ansattnr, tpl.sap_fnr) return None if person is not None: logger.debug("A person owning IDs (%s, %s) already exists", tpl.sap_ansattnr, tpl.sap_fnr) # Now, we *must* check that the IDs registered in Cerebrum match # those in SAP dump. I.e. we select the external IDs from Cerebrum # and compare them to SAP_ID and FNR. They must either match # exactly or be absent. if not match_external_ids(person, tpl.sap_ansattnr, tpl.sap_fnr): return None else: person = Factory.get("Person")(database) logger.debug("New person for IDs (%s, %s)", tpl.sap_ansattnr, tpl.sap_fnr) try: fodselsnr.personnr_ok(tpl.sap_fnr) except fodselsnr.InvalidFnrError: # IVR 2007-02-15 It is *wrong* to simply ignore these, but since they # do occur, and they may be difficult to get rid of, we'll downgrade # the severity to avoid being spammed to death. logger.info("No valid checksum for FNR (%s)!", tpl.sap_fnr) return None gender = const.gender_male if fodselsnr.er_kvinne(tpl.sap_fnr): gender = const.gender_female # This would allow us to update birthdays and gender information for # both new and existing people. person.populate(tpl.sap_birth_date, gender) person.affect_external_id(const.system_sap, const.externalid_fodselsnr, const.externalid_sap_ansattnr) person.populate_external_id(const.system_sap, const.externalid_sap_ansattnr, tpl.sap_ansattnr) person.populate_external_id(const.system_sap, const.externalid_fodselsnr, tpl.sap_fnr) person.write_db() return person
def _add_external_ids(self, entity, id_dict): if int(id_type) == self.co.externalid_fodselsnr: # Check fnr with the fnr module. try: fodselsnr.personnr_ok(id_dict[id_type]) except fodselsnr.InvalidFnrError: raise ABCErrorInData, "fnr not valid: '%s'" % id_dict[id_type] return self.__super._add_external_ids(entity, id_dict)
def process_person(fnr, lname, fname, bewid, set_names): """ Find or create a person; return the person_id corresponding to fnr. Set name for new persons if set_name is True. """ logger.debug("Processing person %s %s (%s)", fname, lname, fnr) try: fodselsnr.personnr_ok(fnr) except fodselsnr.InvalidFnrError: logger.warn("Bad no_ssn |%s|", fnr) return None if fnr2person_id.has_key(fnr): logger.debug("Person with fnr %s exists in Cerebrum", fnr) return fnr2person_id[fnr] # ... otherwise, create a new person person.clear() gender = constants.gender_male if fodselsnr.er_kvinne(fnr): gender = constants.gender_female year, mon, day = fodselsnr.fodt_dato(fnr) person.populate(db.Date(year, mon, day), gender) if bewid: person.affect_external_id(constants.system_migrate, constants.externalid_fodselsnr, constants.externalid_bewatorid) else: person.affect_external_id(constants.system_migrate, constants.externalid_fodselsnr) person.populate_external_id(constants.system_migrate, constants.externalid_fodselsnr, fnr) person.write_db() e_id = person.entity_id logger.info("Created new person with id %s and fnr %s", e_id, fnr) if bewid: person.populate_external_id(constants.system_migrate, constants.externalid_bewatorid, bewid) person.write_db() logger.info("Added BewatorID %s for %s", bewid, fnr) if set_names: if lname and fname: person.affect_names(constants.system_migrate, constants.name_first, constants.name_last) person.populate_name(constants.name_first, fname) person.populate_name(constants.name_last, lname) logger.info("Name %s %s set for person %s", fname, lname, fnr) person.write_db() else: logger.warn("Couldn't set name %s %s for person %s", fname, lname, fnr) fnr2person_id[fnr] = e_id return e_id
def import_data(fname): # Hent betalings-id'er hittil i år processed_payment_ids = {} for row in ppq.get_history_payments( transaction_type=co.pqtt_quota_fill_pay, bank_id_mask='FS:%%'): processed_payment_ids[row['bank_id']] = True for attrs in GeneralDataParser(fname, "betaling"): fnr = fodselsnr.personnr_ok("%06d%05d" % (int(attrs['fodselsdato']), int(attrs['personnr']))) try: person.clear() person.find_by_external_id( co.externalid_fodselsnr, fnr, source_system=co.system_fs) except Errors.NotFoundError: # # This is actually not a real problem as the person in # question usually is registered a day or two after # The proper solution would be to check whether # this error occurs more than twice and issue a warning # if that is the case, but we cannot support that # kind of solution at the time being # For now we log the error each time it occurs but we don't # issue a warning of any kind. # # TODO (Jazz, 2008-01-21): implement some kind of support for # registration of repetitive errors logger.info("Payment for unknown person: %s" % fnr) continue person_id = person.entity_id # Asert that person has a quota_status entry try: row = ppq.find(person_id) except Errors.NotFoundError: ppq.new_quota(person_id) ekstern_betaling_id = ":".join(('FS', attrs['fakturanr'], attrs['detaljlopenr'])) if processed_payment_ids.has_key(ekstern_betaling_id): logger.debug("Already added: %s" % ekstern_betaling_id) continue description = 'FS-betaling' payment_tstamp = attrs.get('dato_betalt', None) try: logger.debug("Add payment %s for %s" % (attrs['belop'], fnr)) pq_util.add_payment(person_id, PPQUtil.PPQUtil.FS, ekstern_betaling_id, float(attrs['belop']), description, payment_tstamp=payment_tstamp, update_program='money2paper') payment_logger.info("Registered %s kr for %i (id: %s)" % ( attrs['belop'], person_id, ekstern_betaling_id)) except db.DatabaseError, msg: db.rollback() logger.warn("Input %s caused DatabaseError:%s" % (attrs, msg)) continue db.commit()
def process_person(fnr): """ Find (or create, if necessary) and return the person_id corresponding to FNR. """ logger.debug("Processing person %s", fnr) if not fodselsnr.personnr_ok(fnr): logger.warn("Bad no_ssn |%s|", fnr) return None # fi if fnr2person_id.has_key(fnr): logger.debug("Person with fnr %s exists in Cerebrum", fnr) return fnr2person_id[fnr] # fi # ... otherwise, create a new person person.clear() gender = constants.gender_male if fodselsnr.er_kvinne(fnr): gender = constants.gender_female # fi year, mon, day = fodselsnr.fodt_dato(fnr) person.populate(db.Date(year, mon, day), gender) person.affect_external_id(constants.system_migrate, constants.externalid_fodselsnr) person.populate_external_id(constants.system_migrate, constants.externalid_fodselsnr, fnr) person.write_db() logger.debug("Created new person with fnr %s", fnr) e_id = person.entity_id fnr2person_id[fnr] = e_id return e_id
def _get_voip_person(self, designation): """Lookup a person by <something>. """ person = Factory.get("Person")(self.db) if self._get_entity_id(designation): try: person.find(self._get_entity_id(designation)) return person except Errors.NotFoundError: pass # fnr? exc = CerebrumError("No person found for designation %r" % designation) id_type, value = self._human_repr2id(designation) if text_type(value).isdigit(): try: fnr = personnr_ok(text_type(value)) person.find_by_external_id(self.const.externalid_fodselsnr, fnr) return person except (InvalidFnrError, Errors.NotFoundError): pass # account? try: account = Factory.get("Account")(self.db) account.find_by_name(text_type(value)) if account.owner_type == self.const.entity_person: return self._get_voip_person(account.owner_id) except Errors.NotFoundError: pass # By other external ids? By name? raise exc
def move_student_callback(self, person_info): """We will only move the student if it has a valid fnr from FS, and it is not currently on a student disk. If the new homedir cannot be determined, user will be moved to a pending disk. process_students moves users from this disk as soon as a proper disk can be determined. Currently we only operate on the disk whose spread is default_spread""" fnr = "%06d%05d" % (int( person_info['fodselsdato']), int(person_info['personnr'])) logger.debug("Callback for %s" % fnr) try: fodselsnr.personnr_ok(fnr) except Exception, e: logger.exception(e) return
def move_student_callback(self, person_info): """We will only move the student if it has a valid fnr from FS, and it is not currently on a student disk. If the new homedir cannot be determined, user will be moved to a pending disk. process_students moves users from this disk as soon as a proper disk can be determined. Currently we only operate on the disk whose spread is default_spread""" fnr = "%06d%05d" % (int(person_info['fodselsdato']), int(person_info['personnr'])) logger.debug("Callback for %s" % fnr) try: fodselsnr.personnr_ok(fnr) except Exception, e: logger.exception(e) return
def _get_fnr(self, person_info): unchecked_fnr = "%06d%05d" % (int( person_info['fodselsdato']), int(person_info['personnr'])) try: fnr = fodselsnr.personnr_ok(unchecked_fnr) logger.info("Processing %s", fnr) return fnr except fodselsnr.InvalidFnrError: logger.warn("Ugyldig fødselsnr: %s", unchecked_fnr) raise fodselsnr.InvalidFnrError
def process_person(fnr, card_id): """ Find or create a person; return the person_id corresponding to fnr. """ logger.debug("Processing person %s", fnr) try: fodselsnr.personnr_ok(fnr) except fodselsnr.InvalidFnrError: logger.warn("Bad no_ssn |%s|", fnr) return None if fnr2person_id.has_key(fnr): logger.debug("Person with fnr %s exists in Cerebrum", fnr) return fnr2person_id[fnr] # ... otherwise, create a new person person.clear() gender = constants.gender_male if fodselsnr.er_kvinne(fnr): gender = constants.gender_female year, mon, day = fodselsnr.fodt_dato(fnr) person.populate(db.Date(year, mon, day), gender) person.affect_external_id(constants.system_migrate, constants.externalid_fodselsnr, constants.externalid_bewatorid) person.populate_external_id(constants.system_migrate, constants.externalid_fodselsnr, fnr) if card_id is not None and card_id != '': person.populate_external_id(constants.system_migrate, constants.externalid_bewatorid, card_id) else: logger.debug("No Bewator-ID found for person %s" % fnr) person.write_db() logger.debug("Created new person with fnr %s and card-ID %s" % (fnr, card_id)) e_id = person.entity_id fnr2person_id[fnr] = e_id return e_id
def parse_person_info(self, sub): self.logger.debug("Parsing %s" % sub.tag) for el in sub.iter(): value = None if el.text: value = el.text.strip().encode("latin1") # TBD: få bort Mellomnavn fra sap? # Per baardj's request, we consider middle names as first names. middle = "" middle = el.find("PersonInfo/Mellomnavn") if middle is not None and middle.text: middle = middle.text.encode("latin1").strip() if el.tag == "Fornavn": if middle: value += " " + middle # TBD: Skal vi fortsatt ta hensyn til '*' og'@' if '*' in value or '@' in value: self.logger.debug("Name contains '@' or '*', ignored") # Since the element is marked as void, there is no need to # process further (we have no guarantee that any data # would make sense and we won't have even more spurious # warnings). return None # TODO: fix self.result.add_name(DataName(self.tag2type[el.tag], value)) elif el.tag == "Etternavn": if '*' in value or '@' in value: self.logger.debug("Name contains '@' or '*', ignored") # Se <Fornavn>. return None # TODO: fix self.result.add_name(DataName(self.tag2type[el.tag], value)) elif el.tag == "Fodselsnummer": self.result.add_id(self.tag2type[el.tag], personnr_ok(value)) elif el.tag == "Ansattnr": self.result.add_id(self.tag2type[el.tag], value) elif el.tag == "Fodselsdato": self.result.birth_date = self._make_mxdate(value) elif el.tag == "Kjonn": self.result.gender = self.tag2type[value] elif sub.tag == "Title": if value: self.result.add_name( DataName(self.tag2type[sub.tag], value))
def recalc_quota_callback(person_info): # Kun kvoteverdier styres av studconfig.xml. De øvrige # parameterene er for komplekse til å kunne uttrykkes der uten å # introdusere nye tag'er. person_id = person_info.get('person_id', None) logger.set_indent(0) if person_id is None: try: fnr = fodselsnr.personnr_ok("%06d%05d" % ( int(person_info['fodselsdato']), int(person_info['personnr']) )) except fodselsnr.InvalidFnrError: logger.warn('Invalid FNR detected') if fnr not in fnr2pid: logger.warn("fnr %r is an unknown person", fnr) return person_id = fnr2pid[fnr] processed_person[person_id] = True logger.debug("callback for %r", person_id) logger.set_indent(3) # Sjekk at personen er underlagt kvoteregimet if person_id not in quota_victims: logger.debug("not a quota victim %r", person_id) logger.set_indent(0) # assert that person does _not_ have quota set_quota(person_id, has_quota=False) return # Sjekk matching mot studconfig.xml quota = None try: profile = autostud.get_profile( person_info, member_groups=person_id_member.get( person_id, [] ), person_affs=person_id_affs.get( person_id, [] ) ) quota = profile.get_pquota(as_list=True) except AutoStud.ProfileHandler.NoMatchingProfiles, msg: # A common situation, so we won't log it profile = None
def parse_person_info(self, sub): self.logger.debug("Parsing %s" % sub.tag) for el in sub.iter(): value = None if el.text: value = el.text.strip().encode("latin1") # TBD: få bort Mellomnavn fra sap? # Per baardj's request, we consider middle names as first names. middle = "" middle = el.find("PersonInfo/Mellomnavn") if middle is not None and middle.text: middle = middle.text.encode("latin1").strip() if el.tag == "Fornavn": if middle: value += " " + middle # TBD: Skal vi fortsatt ta hensyn til '*' og'@' if '*' in value or '@' in value: self.logger.debug("Name contains '@' or '*', ignored") # Since the element is marked as void, there is no need to # process further (we have no guarantee that any data # would make sense and we won't have even more spurious # warnings). return None # TODO: fix self.result.add_name(DataName(self.tag2type[el.tag], value)) elif el.tag == "Etternavn": if '*' in value or '@' in value: self.logger.debug("Name contains '@' or '*', ignored") # Se <Fornavn>. return None # TODO: fix self.result.add_name(DataName(self.tag2type[el.tag], value)) elif el.tag == "Fodselsnummer": self.result.add_id(self.tag2type[el.tag], personnr_ok(value)) elif el.tag == "Ansattnr": self.result.add_id(self.tag2type[el.tag], value) elif el.tag == "Fodselsdato": self.result.birth_date = self._make_mxdate(value) elif el.tag == "Kjonn": self.result.gender = self.tag2type[value] elif sub.tag == "Title": if value: self.result.add_name(DataName(self.tag2type[sub.tag], value))
def recalc_quota_callback(person_info, fnr2pid, quota_victims, kopiavgift_fritak, betaling_fritak): # Kun kvoteverdier styres av studconfig.xml. De øvrige # parameterene er for komplekse til å kunne uttrykkes der uten å # introdusere nye tag'er. person_id = person_info.get('person_id', None) if person_id is None: try: fnr = fodselsnr.personnr_ok("%06d%05d" % (int( person_info['fodselsdato']), int(person_info['personnr']))) except fodselsnr.InvalidFnrError: logger.warn('Invalid FNR detected') if fnr not in fnr2pid: logger.warn("fnr %r is an unknown person", fnr) return person_id = fnr2pid[fnr] processed_person[person_id] = True logger.debug("callback for %r", person_id) # Sjekk at personen er underlagt kvoteregimet if person_id not in quota_victims: logger.debug("not a quota victim %r", person_id) # assert that person does _not_ have quota set_pq_exempt(person_id, exempt=True) return # Har fritak fra kvote if person_id in betaling_fritak: logger.debug("%s is exempt from quota", person_id) set_pq_exempt(person_id, exempt=True) return # Set quota for those without har kopiavgift-fritak if person_id not in kopiavgift_fritak: logger.debug("block %r (fritak=%r)", person_id, kopiavgift_fritak.get(person_id, False)) set_pq_exempt(person_id, exempt=False) return set_pq_exempt(person_id, exempt=False)
def process_lines(infile): """ Scan all lines in INFILE and create corresponding account entries in Cerebrum. """ stream = open(infile, 'r') commit_count = 0 commit_limit = 1000 # Iterate over all persons: for line in stream: commit_count += 1 logger.debug5("Processing line: |%s|", line.strip()) fields = string.split(line.strip(), ";") if len(fields) != 2: logger.error("Bad line: %s. Skipping" % l) continue uname, fnr = fields try: fnr = fodselsnr.personnr_ok(fnr) except fodselsnr.InvalidFnrError: logger.error("Bad fnr: %s, uname %s. Skipping!" % (fnr, uname)) continue person_id = process_person(fnr) logger.debug4("Processing person with user: %s", uname) account_id = process_user(person_id, uname) if not account_id: logger.error("Bad uname: %s Skipping", line) if commit_count % commit_limit == 0: attempt_commit()
def recalc_quota_callback(person_info): # Kun kvoteverdier styres av studconfig.xml. De øvrige # parameterene er for komplekse til å kunne uttrykkes der uten å # introdusere nye tag'er. person_id = person_info.get('person_id', None) logger.set_indent(0) if person_id is None: fnr = fodselsnr.personnr_ok( "%06d%05d" % (int(person_info['fodselsdato']), int(person_info['personnr']))) if fnr not in fnr2pid: logger.warn("fnr %s is an unknown person" % fnr) return person_id = fnr2pid[fnr] processed_person[person_id] = True logger.debug("callback for %s" % person_id) logger.set_indent(3) # Sjekk at personen er underlagt kvoteregimet if person_id not in quota_victims: logger.debug("not a quota victim %s" % person_id) logger.set_indent(0) # assert that person does _not_ have quota set_quota(person_id, has_quota=False) return # Sjekk matching mot studconfig.xml quota = None try: profile = autostud.get_profile( person_info, member_groups=person_id_member.get(person_id, []), person_affs=person_id_affs.get(person_id, [])) quota = profile.get_pquota(as_list=True) except AutoStud.ProfileHandler.NoMatchingProfiles, msg: # A common situation, so we won't log it profile = None
def next_object(self, element): """Return the next SAPPerson object. Consume the next XML-element describing a person, and return a suitable representation (SAPPerson). Should something fail (which prevents this method from constructing a proper SAPPerson object), an exception is raised. """ result = SAPPerson() # Per baardj's request, we consider middle names as first names. middle = "" middle = element.find("Person/Mellomnavn") if middle is not None and middle.text: middle = ensure_unicode(middle.text.strip(), self.encoding) # Iterate over *all* subelements, 'fill up' the result object for sub in element.getiterator(): value = None if sub.text: value = ensure_unicode(sub.text.strip(), self.encoding) if sub.tag == "Fornavn": if middle: value += " " + middle # IVR 2007-05-30 FIXME: This is not pretty. # # In an e-mail from 2007-05-29, Johannes Paulsen suggests that # marking invalid entries with '*' in the some of the name # elements is the easiest approach. This is an ugly hack, but # since the invalid entries will not disappear anytime soon, # this is the easiest way of skipping them. # # JAZZ 2007-08-01 # '*' did not work all that well as it is used as common # wildcard in SAP. Johannes suggests that we use '@' in # stead. As the data is not updated yet (we don't know when # that will happen) we need to test for '*' as well in order # to skip all the invalid elements # if '*' in value or '@' in value: if self.logger: self.logger.debug("Name contains '@' or '*', ignored") # Since the element is marked as void, there is no need to # process further (we have no guarantee that any data # would make sense and we won't have even more spurious # warnings). return None result.add_name(DataName(self.tag2type[sub.tag], value)) elif sub.tag == "Etternavn": if '*' in value or '@' in value: if self.logger: self.logger.debug("Name contains '@' or '*', ignored") # Se <Fornavn>. return None result.add_name(DataName(self.tag2type[sub.tag], value)) elif sub.tag == "Fodselsnummer" and value is not None: result.add_id(self.tag2type[sub.tag], personnr_ok(value)) elif sub.tag == "Ansattnummer": result.add_id(self.tag2type[sub.tag], value) self.logger.debug(value) elif sub.tag == "Fodselsdato": result.birth_date = self._make_mxdate(value, format="%Y-%m-%d") elif sub.tag == "Kjonn": result.gender = self.tag2type[value] elif sub.tag == "Adresse": result.add_address(self._make_address(sub)) elif sub.tag in ("Hovedstilling", "Bistilling"): emp = self._make_employment(sub) if emp is not None: result.add_employment(emp) elif sub.tag == "Roller" and sub.findtext("IKKE-ANGIT") is None: emp = self._make_role(sub) if emp is not None: result.add_employment(emp) elif sub.tag == "Person": # Lots of the other entries above also are part of the # "person"-firstlevel element, but we need to # specifically look here for Tittel => personal title, # to avoid confusion with worktitles for subsub in sub.findall("Tittel"): personal_title = self._make_title(HRDataPerson.NAME_TITLE, subsub) if personal_title: result.add_name(personal_title) elif sub.tag == "PersonligID": # Store additional person ids, like passport numbers. # Handle passport numbers if sub.find('Type').text in self.sap2idtype: # Add the ID to the data-structure pers_id = '{0}-{1}'.format( ensure_unicode(sub.find('Land').text, self.encoding), ensure_unicode(sub.find('Verdi').text, self.encoding) ) result.add_id(self.sap2idtype[sub.find('Type').text], pers_id) else: self.logger.debug( "Unknown %s type '%s': skipping id type", sub.tag, sub.find('Type').text) elif sub.tag == "SGM": # New feature and unique (for now?) for UiO is SGM, # external attachments for person. self.logger.debug("SGM for %s", result) result.add_external_work(self._make_sgm(sub)) # We need to order 'Telefon 1' and 'Telefon 2' properly celems = list(element.findall("Kommunikasjon")) celems.sort(lambda x, y: cmp(x.find("Type").text, y.find("Type").text)) # TBD: Priorities! priority = 0 for ct in celems: contact = self._make_contact(ct, priority) if contact: result.add_contact(contact) priority += 1 # Reservations for catalogue publishing # default: One active employment => can be published to_reserve = not result.has_active_employments() # Everyone with 'RESE' is reserved (regardless of everything else) # If no 'RESE' exists, but there is a 'SAMT' => no reservation for i in element.findall("Adresse/Reservert"): if i.text: tmp = i.text.strip() if tmp == "RESE": to_reserve = True break elif tmp == "SAMT": to_reserve = False result.reserved = to_reserve # Address magic # If there is a sensible 'Sted for lønnsslipp', it will result i # proper "post/besøksaddresse" later. This code matches LT's behaviour # more closely (an employee 'inherits' the address of his/her # "primary" workplace. for sub in element.getiterator("Kommunikasjon"): txt = ensure_unicode(sub.findtext("Type"), self.encoding) val = ensure_unicode(sub.findtext("Verdi"), self.encoding) if (txt and txt == "Sted for lønnslipp" and val # *some* of the entries have a space here and there. # and some contain non-digit data and val.replace(" ", "").isdigit()): val = val.replace(" ", "") fak, inst, gruppe = [int(x) for x in (val[:2], val[2:4], val[4:])] result.primary_ou = (cereconf.DEFAULT_INSTITUSJONSNR, fak, inst, gruppe) # We require people to have first/last name. if not (result.get_name(result.NAME_FIRST) and result.get_name(result.NAME_LAST)): self.logger.warn( "People must have first and last names. %s skipped", list(result.iterids()) ) return None return result
def move_student_callback(person_info): """We will only move the student if it has a valid fnr from FS, and it is not currently on a student disk. If the new homedir cannot be determined, user will be moved to a pending disk. process_students moves users from this disk as soon as a proper disk can be determined. Currently we only operate on the disk whose spread is default_spread""" fnr = fodselsnr.personnr_ok("%06d%05d" % (int(person_info['fodselsdato']), int(person_info['personnr']))) if fnr not in fnr2move_student: return logger.debug("Callback for %s" % fnr) account = Utils.Factory.get('Account')(db) group = Utils.Factory.get('Group')(db) br = BofhdRequests(db, const) for account_id, request_id, requestee_id in fnr2move_student.get(fnr, []): account.clear() account.find(account_id) groups = list(int(x["group_id"]) for x in group.search(member_id=account_id, indirect_members=False)) try: profile = autostud.get_profile(person_info, member_groups=groups) logger.debug(profile.matcher.debug_dump()) except AutostudError, msg: logger.debug("Error getting profile, using pending: %s" % msg) continue # Determine disk disks = [] spreads = [int(s) for s in profile.get_spreads()] try: for d_spread in profile.get_disk_spreads(): if d_spread != default_spread: # TBD: How can all spreads be taken into account? continue if d_spread in spreads: try: ah = account.get_home(d_spread) homedir_id = ah['homedir_id'] current_disk_id = ah['disk_id'] except Errors.NotFoundError: homedir_id, current_disk_id = None, None if autostud.disk_tool.get_diskdef_by_diskid( int(current_disk_id)): logger.debug("Already on a student disk") br.delete_request(request_id=request_id) db.commit() # actually, we remove a bit too much data from # the below dict, but remaining data will be # rebuilt on next run. del(fnr2move_student[fnr]) raise NextAccount try: new_disk = profile.get_disk(d_spread, current_disk_id, do_check_move_ok=False) if new_disk == current_disk_id: continue disks.append((new_disk, d_spread)) if (autostud.disk_tool.using_disk_kvote and homedir_id is not None): from Cerebrum.modules.no.uio import DiskQuota disk_quota_obj = DiskQuota.DiskQuota(db) try: cur_quota = disk_quota_obj.get_quota( homedir_id) except Errors.NotFoundError: cur_quota = None quota = profile.get_disk_kvote(new_disk) if (cur_quota is None or cur_quota['quota'] != int(quota)): disk_quota_obj.set_quota(homedir_id, quota=int(quota)) except AutostudError, msg: # Will end up on pending (since we only use one spread) logger.debug("Error getting disk: %s" % msg) break except NextAccount: pass # Stupid python don't have labeled breaks logger.debug(str((fnr, account_id, disks))) if disks: logger.debug("Destination %s" % repr(disks)) del(fnr2move_student[fnr]) for disk, spread in disks: br.delete_request(request_id=request_id) br.add_request(requestee_id, br.batch_time, const.bofh_move_user, account_id, disk, state_data=spread) db.commit()
def process_person_callback(person_info): """Called when we have fetched all data on a person from the xml file. Updates/inserts name, address and affiliation information.""" global no_name try: fnr = fodselsnr.personnr_ok("%06d%05d" % (int(person_info["fodselsdato"]), int(person_info["personnr"]))) fnr = fodselsnr.personnr_ok(fnr) logger.info("Process %s " % (fnr)) (year, mon, day) = fodselsnr.fodt_dato(fnr) if year < 1970 and getattr(cereconf, "ENABLE_MKTIME_WORKAROUND", 0) == 1: # Seems to be a bug in time.mktime on some machines year = 1970 except fodselsnr.InvalidFnrError: logger.warn("Ugyldig fødselsnr: %s" % fnr) return gender = co.gender_male if fodselsnr.er_kvinne(fnr): gender = co.gender_female etternavn = fornavn = None studentnr = None affiliations = [] address_info = None aktiv_sted = [] # Iterate over all person_info entries and extract relevant data if person_info.has_key("aktiv"): for row in person_info["aktiv"]: if studieprog2sko[row["studieprogramkode"]] is not None: aktiv_sted.append(int(studieprog2sko[row["studieprogramkode"]])) logger.debug("App2akrivts") for dta_type in person_info.keys(): x = person_info[dta_type] p = x[0] if isinstance(p, str): continue # Get name if dta_type in ("fagperson", "evu", "aktiv"): etternavn = p["etternavn"] fornavn = p["fornavn"] if p.has_key("studentnr_tildelt"): studentnr = "%06d" % int(p["studentnr_tildelt"]) # Get affiliations if dta_type in ("fagperson",): _process_affiliation( co.affiliation_tilknyttet, co.affiliation_status_tilknyttet_fagperson, affiliations, _get_sko(p, "faknr", "instituttnr", "gruppenr", "institusjonsnr"), ) elif dta_type in ("aktiv",): for row in x: # aktiv_sted is necessary in order to avoid different affiliation statuses # to a same 'stedkode' to be overwritten # e.i. if a person has both affiliations status 'evu' and # aktive to a single stedkode we want to register the status 'aktive' # in cerebrum if studieprog2sko[row["studieprogramkode"]] is not None: aktiv_sted.append(int(studieprog2sko[row["studieprogramkode"]])) _process_affiliation( co.affiliation_student, co.affiliation_status_student_aktiv, affiliations, studieprog2sko[row["studieprogramkode"]], ) elif dta_type in ("evu",): subtype = co.affiliation_status_student_evu if studieprog2sko[row["studieprogramkode"]] in aktiv_sted: subtype = co.affiliation_status_student_aktiv _process_affiliation( co.affiliation_student, subtype, affiliations, studieprog2sko[row["studieprogramkode"]] ) if etternavn is None: logger.debug("Ikke noe navn på %s" % fnr) no_name += 1 return new_person = Factory.get("Person")(db) if fnr2person_id.has_key(fnr): new_person.find(fnr2person_id[fnr]) new_person.populate(mx.DateTime.Date(year, mon, day), gender) new_person.affect_names(co.system_fs, co.name_first, co.name_last) new_person.populate_name(co.name_first, fornavn) new_person.populate_name(co.name_last, etternavn) if studentnr is not None: new_person.affect_external_id( co.system_fs, co.externalid_fodselsnr, co.externalid_studentnr, co.externalid_bewatorid ) new_person.populate_external_id(co.system_fs, co.externalid_studentnr, studentnr) logger.debug("Studentnr is %s", studentnr) new_bew_id = "01221%06d0" % int(studentnr) logger.debug("Adding bewator-ID %s for %s", new_bew_id, studentnr) # we have to use system_fs here (for technical reasons) even # though we should be using system_manual new_person.populate_external_id(co.system_fs, co.externalid_bewatorid, new_bew_id) else: new_person.affect_external_id(co.system_fs, co.externalid_fodselsnr) new_person.populate_external_id(co.system_fs, co.externalid_fodselsnr, fnr) ad_post, ad_post_private, ad_street = _calc_address(person_info) for address_info, ad_const in ( (ad_post, co.address_post), (ad_post_private, co.address_post_private), (ad_street, co.address_street), ): # TBD: Skal vi slette evt. eksisterende adresse v/None? if address_info is not None: logger.debug("Populating address...") new_person.populate_address(co.system_fs, ad_const, **address_info) # if this is a new Person, there is no entity_id assigned to it # until written to the database. op = new_person.write_db() for a in filter_affiliations(affiliations): ou, aff, aff_status = a new_person.populate_affiliation(co.system_fs, ou, aff, aff_status) if include_delete: key_a = "%s:%s:%s" % (new_person.entity_id, ou, int(aff)) if old_aff.has_key(key_a): old_aff[key_a] = False register_cellphone(new_person, person_info) op2 = new_person.write_db() if op is None and op2 is None: logger.info("**** EQUAL ****") elif op == True: logger.info("**** NEW ****") else: logger.info("**** UPDATE ****") # Reservations if gen_groups: should_add = False for dta_type in person_info.keys(): p = person_info[dta_type][0] if isinstance(p, str): continue # Presence of 'fagperson' elements for a person should not # affect that person's reservation status. if dta_type in ("fagperson",): continue # We only fetch the column in these queries if dta_type not in ("evu"): continue # If 'status_reserv_nettpubl' == "N": add to group if p.get("status_reserv_nettpubl", "") == "N": should_add = True else: should_add = False if should_add: # The student has explicitly given us permission to be # published in the directory. _add_res(new_person.entity_id) else: # The student either hasn't registered an answer to # the "Can we publish info about you in the directory" # question at all, or has given an explicit "I don't # want to appear in the directory" answer. _rem_res(new_person.entity_id) db.commit()
def _process_student(person_info): fnr = fodselsnr.personnr_ok( "%06d%05d" % (int(person_info['fodselsdato']), int(person_info['personnr']))) logger.set_indent(0) logger.debug("Callback for %s", fnr) logger.set_indent(3) pinfo = persons.get(fnr, None) if pinfo is None: logger.warn("Unknown person %s", fnr) return logger.debug(pformat(_filter_person_info(person_info))) if fnr not in persons: logger.warn("(person) not found error for %s", fnr) logger.set_indent(0) return try: profile = autostud.get_profile( person_info, member_groups=persons[fnr].get_groups(), person_affs=persons[fnr].get_affiliations()) logger.debug(profile.matcher.debug_dump()) except AutoStud.ProfileHandler.NoMatchingProfiles as msg: logger.warn("No matching profile error for %s: %s", fnr, msg) logger.set_indent(0) return except AutoStud.ProfileHandler.NoAvailableDisk as msg: # pretend that the account was processed so that # list_noncallback_users doesn't include the user(s). # While this is only somewhat correct behaviour, the # NoAvailableDisk situation should be resolved switftly. for account_id in pinfo.get_student_ac(): processed_accounts[account_id] = True raise processed_students[fnr] = 1 keep_account_home[fnr] = profile.get_build()['home'] if fast_test: logger.debug(profile.debug_dump()) # logger.debug("Disk: %s", profile.get_disk()) logger.set_indent(0) return try: _debug_dump_profile_match(profile, fnr) if dryrun: logger.set_indent(0) return if (create_users and not pinfo.has_student_ac() and profile.get_build()['action']): if pinfo.has_other_ac(): logger.debug("Has active non-student account, skipping") return elif pinfo.has_reserved_ac(): # has a reserved account logger.debug("using reserved: %s", pinfo.get_best_reserved_ac()) BuildAccounts._update_persons_accounts( profile, fnr, [pinfo.get_best_reserved_ac()]) elif pinfo.has_deleted_ac(): logger.debug("using deleted: %s", pinfo.get_best_deleted_ac()) BuildAccounts._update_persons_accounts( profile, fnr, [pinfo.get_best_deleted_ac()]) else: account_id = AccountUtil.create_user(fnr, profile) logger.debug("would create account for %s", fnr) if account_id is None: logger.set_indent(0) return # students.setdefault(fnr, {})[account_id] = [] elif update_accounts and pinfo.has_student_ac(): BuildAccounts._update_persons_accounts(profile, fnr, pinfo.get_student_ac()) except AutoStud.ProfileHandler.NoAvailableDisk as msg: logger.error(" Error for %s: %s", fnr, msg) logger.set_indent(0) # We commit once for each person to avoid locking too many db-rows if not dryrun: db.commit()
def person_student_info(self, operator, person_id): person_exists = False person = None try: person = self._get_person(*self._map_person_id(person_id)) person_exists = True except CerebrumError as e: # Check if person exists in FS, but is not imported yet, e.g. # emnestudents. These should only be listed with limited # information. if person_id and len(person_id) == 11 and person_id.isdigit(): try: person_id = fodselsnr.personnr_ok(person_id) except Exception: raise e self.logger.debug('Unknown person %r, asking FS directly', person_id) self.ba.can_get_student_info(operator.get_entity_id(), None) fodselsdato, pnum = person_id[:6], person_id[6:] else: raise e else: self.ba.can_get_student_info(operator.get_entity_id(), person) fnr = person.get_external_id( id_type=self.const.externalid_fodselsnr, source_system=self.const.system_fs) if not fnr: raise CerebrumError("No matching fnr from FS") fodselsdato, pnum = fodselsnr.del_fnr(fnr[0]['external_id']) ret = [] try: db = database.connect(user=cereconf.FS_USER, service=cereconf.FS_DATABASE_NAME, DB_driver=cereconf.DB_DRIVER_ORACLE) except database.DatabaseError as e: self.logger.warn("Can't connect to FS (%s)", text_type(e)) raise CerebrumError("Can't connect to FS, try later") fs = FS(db) for row in fs.student.get_undervisningsmelding(fodselsdato, pnum): ret.append({ 'undvkode': row['emnekode'], 'dato': row['dato_endring'], }) har_opptak = set() if person_exists: for row in fs.student.get_studierett(fodselsdato, pnum): har_opptak.add(row['studieprogramkode']) ret.append({ 'studprogkode': row['studieprogramkode'], 'studierettstatkode': row['studierettstatkode'], 'studentstatkode': row['studentstatkode'], 'studieretningkode': row['studieretningkode'], 'dato_tildelt': row['dato_studierett_tildelt'], 'dato_gyldig_til': row['dato_studierett_gyldig_til'], 'privatist': row['status_privatist'], }) for row in fs.student.get_eksamensmeldinger(fodselsdato, pnum): programmer = [] for row2 in fs.info.get_emne_i_studieprogram(row['emnekode']): if row2['studieprogramkode'] in har_opptak: programmer.append(row2['studieprogramkode']) ret.append({ 'ekskode': row['emnekode'], 'programmer': ",".join(programmer), 'dato': row['dato_opprettet'], }) for row in fs.student.get_utdanningsplan(fodselsdato, pnum): ret.append({ 'studieprogramkode': row['studieprogramkode'], 'terminkode_bekreft': row['terminkode_bekreft'], 'arstall_bekreft': row['arstall_bekreft'], 'dato_bekreftet': row['dato_bekreftet'], }) def _ok_or_not(input): """Helper function for proper feedback of status.""" if not input or input == 'N': return 'Nei' if input == 'J': return 'Ja' return input semregs = tuple(fs.student.get_semreg(fodselsdato, pnum, only_valid=False)) for row in semregs: ret.append({ 'regstatus': _ok_or_not(row['status_reg_ok']), 'regformkode': row['regformkode'], 'dato_endring': row['dato_endring'], 'dato_regform_endret': row['dato_regform_endret'], }) ret.append({ 'betstatus': _ok_or_not(row['status_bet_ok']), 'betformkode': row['betformkode'], 'dato_betaling': row['dato_betaling'], }) # The semreg and sembet lines should always be sent, to make it # easier for the IT staff to see if a student have paid or not. if not semregs: ret.append({ 'regstatus': 'Nei', 'regformkode': None, 'dato_endring': None, 'dato_regform_endret': None, }) ret.append({ 'betstatus': 'Nei', 'betformkode': None, 'dato_betaling': None, }) db.close() return ret
def next_object(self, element): """Return the next SAPPerson object. Consume the next XML-element describing a person, and return a suitable representation (SAPPerson). Should something fail (which prevents this method from constructing a proper SAPPerson object), an exception is raised. """ def to_latin1(string): import codecs import unicodedata try: return codecs.encode(string, 'latin1') except UnicodeEncodeError: return codecs.encode(unicodedata.normalize('NFKD', string), 'latin1', 'ignore') result = SAPPerson() # Per baardj's request, we consider middle names as first names. middle = "" middle = element.find("Person/Mellomnavn") if middle is not None and middle.text: middle = to_latin1(middle.text).strip() # Iterate over *all* subelements, 'fill up' the result object for sub in element.getiterator(): value = None if sub.text: value = to_latin1(sub.text.strip()) if sub.tag == "Fornavn": if middle: value += " " + middle # IVR 2007-05-30 FIXME: This is not pretty. # # In an e-mail from 2007-05-29, Johannes Paulsen suggests that # marking invalid entries with '*' in the some of the name # elements is the easiest approach. This is an ugly hack, but # since the invalid entries will not disappear anytime soon, # this is the easiest way of skipping them. # # JAZZ 2007-08-01 # '*' did not work all that well as it is used as common # wildcard in SAP. Johannes suggests that we use '@' in # stead. As the data is not updated yet (we don't know when # that will happen) we need to test for '*' as well in order # to skip all the invalid elements # if '*' in value or '@' in value: if self.logger: self.logger.debug("Name contains '@' or '*', ignored") # Since the element is marked as void, there is no need to # process further (we have no guarantee that any data # would make sense and we won't have even more spurious # warnings). return None result.add_name(DataName(self.tag2type[sub.tag], value)) elif sub.tag == "Etternavn": if '*' in value or '@' in value: if self.logger: self.logger.debug("Name contains '@' or '*', ignored") # Se <Fornavn>. return None result.add_name(DataName(self.tag2type[sub.tag], value)) elif sub.tag == "Fodselsnummer" and value is not None: result.add_id(self.tag2type[sub.tag], personnr_ok(value)) elif sub.tag == "Ansattnummer": result.add_id(self.tag2type[sub.tag], value) self.logger.debug(value) elif sub.tag == "Fodselsdato": result.birth_date = self._make_mxdate(value, format="%Y-%m-%d") elif sub.tag == "Kjonn": result.gender = self.tag2type[value] elif sub.tag == "Adresse": result.add_address(self._make_address(sub)) elif sub.tag in ("Hovedstilling", "Bistilling"): emp = self._make_employment(sub) if emp is not None: result.add_employment(emp) elif sub.tag == "Roller" and sub.findtext("IKKE-ANGIT") is None: emp = self._make_role(sub) if emp is not None: result.add_employment(emp) elif sub.tag == "Person": # Lots of the other entries above also are part of the # "person"-firstlevel element, but we need to # specifically look here for Tittel => personal title, # to avoid confusion with worktitles for subsub in sub.findall("Tittel"): personal_title = self._make_title(HRDataPerson.NAME_TITLE, subsub) if personal_title: result.add_name(personal_title) elif sub.tag == "PersonligID": # Store additional person ids, like passport numbers. # Handle passport numbers if sub.find('Type').text in self.sap2idtype: # Add the passport number to the data-structure result.add_id( self.sap2idtype[sub.find('Type').text], "%s-%s" % (sub.find('Land').text, sub.find('Verdi').text)) else: self.logger.debug("Unknown %s type '%s': skipping id type", sub.tag, sub.find('Type').text) elif sub.tag == "SGM": # New feature and unique (for now?) for UiO is SGM, # external attachments for person. self.logger.debug("SGM for %s", result) result.add_external_work(self._make_sgm(sub)) # We need to order 'Telefon 1' and 'Telefon 2' properly celems = list(element.findall("Kommunikasjon")) celems.sort(lambda x, y: cmp(x.find("Type").text, y.find("Type").text)) # TBD: Priorities! priority = 0 for ct in celems: contact = self._make_contact(ct, priority) if contact: result.add_contact(contact) priority += 1 # Reservations for catalogue publishing # default: One active employment => can be published to_reserve = not result.has_active_employments() # Everyone with 'RESE' is reserved (regardless of everything else) # If no 'RESE' exists, but there is a 'SAMT' => no reservation for i in element.findall("Adresse/Reservert"): if i.text: tmp = i.text.strip() if tmp == "RESE": to_reserve = True break elif tmp == "SAMT": to_reserve = False result.reserved = to_reserve # Address magic # If there is a sensible 'Sted for lønnsslipp', it will result i # proper "post/besøksaddresse" later. This code matches LT's behaviour # more closely (an employee 'inherits' the address of his/her # "primary" workplace. for sub in element.getiterator("Kommunikasjon"): txt = sub.findtext("Type") val = sub.findtext("Verdi") if (txt and txt.encode("latin1") == "Sted for lønnslipp" and val # *some* of the entries have a space here and there. # and some contain non-digit data and val.replace(" ", "").isdigit()): val = val.replace(" ", "") fak, inst, gruppe = [ int(x) for x in (val[:2], val[2:4], val[4:]) ] result.primary_ou = (cereconf.DEFAULT_INSTITUSJONSNR, fak, inst, gruppe) # We require people to have first/last name. if not (result.get_name(result.NAME_FIRST) and result.get_name(result.NAME_LAST)): self.logger.warn( "People must have first and last names. %s skipped", list(result.iterids())) return None return result
def new_project(self, input): """Create a given project. TODO: A lot of this code should be moved into e.g. TSD's OU mixin, or somewhere else to be usable both by various scripts and bofhd. @type input: dict @param input: The survey answers about the requested project. """ pname = input['p_id'] logger.info('New project: %s', pname) ou = self._create_ou(input) # Update the requestee for the project: pe = self._get_person() self._update_person(pe, input) # Give the respondent an affiliation to the project. # If the respondent sets himself as the Project Owner (responsible), it # gets status as the owner. Otherwise we give him PA status: # TBD: do we need to differentiate between owner and PA? status = co.affiliation_status_project_admin if self.fnr == input['rek_owner']: status = co.affiliation_status_project_owner pe.populate_affiliation(source_system=co.system_nettskjema, ou_id=ou.entity_id, status=status, affiliation=co.affiliation_project) pe.write_db() # Check the responsible and give access to the project by an # affiliation: if self.fnr != input['rek_owner']: # TODO: Should we create a person with this ID or not? try: pe2 = self._get_person(input['rek_owner'], create_nonexisting=False) pe2.populate_affiliation(source_system=co.system_nettskjema, ou_id=ou.entity_id, affiliation=co.affiliation_project, status=co.affiliation_status_project_owner) # Note that no name or anything else is now set, so we wait with the # account. pe2.write_db() except Errors.NotFoundError: logger.warn("Project owner not found: %s", input['rek_owner']) # Give the PA an account: ac = self._create_account(pe, ou, input['pa_username']) # Fill the pre approve list with external ids: pre_approve_list = set() for fnr in set(input['p_persons'].split()): for fnr1 in set(fnr.split(',')): try: fnr1 = fodselsnr.personnr_ok(fnr1) except fodselsnr.InvalidFnrError: logger.debug("Ignoring invalid fnr: %s", fnr1) continue except ValueError: logger.debug("Ignoring invalid fnr: %s", fnr1) continue pre_approve_list.add(fnr1) if pre_approve_list: logger.debug("Pre approvals: %s", ', '.join(pre_approve_list)) ou.add_pre_approved_persons(pre_approve_list) ou.write_db() # TODO: How should we signal that a new project is waiting for approval? return True
def fetch_data(drgrad_file, fritak_kopiavg_file, betalt_papir_file, sysname, person_file): """Finner alle personer som rammes av kvoteordningen ved å: - finne alle som har en student-affiliation (0.1) - ta bort ansatte (1.2.1) - ta bort dr-grads stipendiater (1.2.2) I.e. vi fjerner alle som har fullstendig fritak fra ordningen. De som kun har fritak fra kopiavgiften er ikke fjernet. Finner alle gruppe-medlemskap, enten via person eller account for de som rammes av ordningen. Finner fritak fra kopiavgift (1.2.3 og 1.2.4) """ logger.debug("Prefetching data") betaling_fritak = get_bet_fritak_utv_data(sysname, person_file) logger.debug2("Fritak for: %r", betaling_fritak) # Finn alle som skal rammes av kvoteregimet quota_victim = {} for pid in get_students(): quota_victim[pid] = True # Ta bort de som ikke har konto account = Account.Account(db) account_id2pid = {} has_account = {} for row in account.list_accounts_by_type(fetchall=False): account_id2pid[int(row['account_id'])] = int(row['person_id']) has_account[int(row['person_id'])] = True for p_id in quota_victim.keys(): if p_id not in has_account: del(quota_victim[p_id]) logger.debug("after removing non-account people: %i" % len(quota_victim)) # Ansatte har fritak # TODO: sparer litt ytelse ved å gjøre dette i get_students() for row in person.list_affiliations( affiliation=const.affiliation_ansatt, status=(const.affiliation_status_ansatt_bil, const.affiliation_status_ansatt_vit, const.affiliation_status_ansatt_tekadm,), source_system=const.system_sap, include_deleted=False, fetchall=False ): if int(row['person_id']) in quota_victim: del(quota_victim[int(row['person_id'])]) logger.debug("removed employees: %i" % len(quota_victim)) # Alle personer som har disse typer tilknyttet affiliation skal ha fritak for row in person.list_affiliations( affiliation=const.affiliation_tilknyttet, status=(const.affiliation_tilknyttet_bilag, const.affiliation_tilknyttet_ekst_forsker, const.affiliation_tilknyttet_ekst_partner, const.affiliation_tilknyttet_emeritus, const.affiliation_tilknyttet_gjesteforsker, const.affiliation_tilknyttet_innkjoper,), source_system=const.system_sap, include_deleted=False, fetchall=False ): if int(row['person_id']) in quota_victim: del(quota_victim[int(row['person_id'])]) logger.debug("removed tilknyttet people: %i" % len(quota_victim)) # Mappe fødselsnummer til person-id fnr2pid = {} for p in person.search_external_ids(source_system=const.system_fs, id_type=const.externalid_fodselsnr, fetchall=False): fnr2pid[p['external_id']] = int(p['entity_id']) # Dr.grads studenter har fritak for row in GeneralDataParser(drgrad_file, "drgrad"): fnr = fodselsnr.personnr_ok("%06d%05d" % (int(row['fodselsdato']), int(row['personnr']))) pid = fnr2pid.get(fnr, None) if not pid: continue if pid in quota_victim: del(quota_victim[pid]) logger.debug("removed drgrad: %i" % len(quota_victim)) # map person-id til gruppemedlemskap. Hvis gruppe-medlemet er av # typen konto henter vi ut owner_id. person_id_member = {} group = Group.Group(db) count = [0, 0] groups = autostud.pc.group_defs.keys() logger.debug("Finding members in %s" % groups) for g in groups: group.clear() group.find(g) for m in set([int(row["member_id"]) for row in group.search_members(group_id=group.entity_id, indirect_members=True, member_type=const.entity_account)]): if m in account_id2pid: person_id_member.setdefault(account_id2pid[m], []).append(g) count[0] += 1 else: person_id_member.setdefault(m, []).append(g) count[1] += 1 logger.debug("memberships: persons:%i, p_m=%i, a_m=%i", len(person_id_member), count[1], count[0]) # Fetch any affiliations used as select criteria person_id_affs = {} for sm in autostud.pc.select_tool.select_map_defs.values(): if not isinstance(sm, AutoStud.Select.SelectMapPersonAffiliation): continue for aff_attrs in sm._select_map.keys(): affiliation = aff_attrs[0] for row in person.list_affiliations( affiliation=affiliation, include_deleted=False, fetchall=False ): person_id_affs.setdefault(int(row['person_id']), []).append( (int(row['ou_id']), int(row['affiliation']), int(row['status']))) # fritak fra selve kopiavgiften (1.2.3 og 1.2.4) kopiavgift_fritak = {} for row in GeneralDataParser(fritak_kopiavg_file, "betfritak"): fnr = fodselsnr.personnr_ok("%06d%05d" % (int(row['fodselsdato']), int(row['personnr']))) pid = fnr2pid.get(fnr, None) if not pid: continue kopiavgift_fritak[pid] = True logger.debug("%i personer har kopiavgiftsfritak" % len(kopiavgift_fritak)) # De som har betalt kopiavgiften har_betalt = {} for row in GeneralDataParser(betalt_papir_file, "betalt"): fnr = fodselsnr.personnr_ok("%06d%05d" % (int(row['fodselsdato']), int(row['personnr']))) pid = fnr2pid.get(fnr, None) if not pid: continue har_betalt[pid] = True logger.debug("%i personer har betalt kopiavgift" % len(har_betalt)) # Hent gratis-tildelinger hittil i år free_this_term = {} for row in ppq.get_history_payments( transaction_type=const.pqtt_quota_fill_free, desc_mask=term_init_prefix+'%%' ): free_this_term.setdefault(int(row['person_id']), []).append( row['description'][len(term_init_prefix):]) logger.debug("free_this_term: %i" % len(free_this_term)) logger.debug("Prefetch returned %i students" % len(quota_victim)) n = 0 for pid in betaling_fritak.keys(): if pid in quota_victim: n += 1 logger.debug("%i av disse har betaling_fritak" % n) return (fnr2pid, quota_victim, person_id_member, person_id_affs, kopiavgift_fritak, har_betalt, free_this_term, betaling_fritak)
def next_object(self, element): def get_value(element_value): return ensure_unicode(element_value, self.encoding) def extract(element_attr): return get_value(element.get(element_attr, "")) result = HRDataPerson() # Pull out all names self._register_names(result, element) # Pull out fnr tmp = "%02d%02d%02d%05d" % tuple([ int(element.get(x)) for x in ("fodtdag", "fodtmnd", "fodtar", "personnr") ]) fnr = fodselsnr.personnr_ok(tmp) result.add_id(result.NO_SSN, fnr) # Since LT does not provide birth date directly, we extract it from fnr result.birth_date = Date(*fodselsnr.fodt_dato(fnr)) # ... and gender if fodselsnr.er_mann(fnr): result.gender = result.GENDER_MALE else: result.gender = result.GENDER_FEMALE # fi # Register address # extract = lambda y: ensure_unicode(element.get(y, ""), self.encoding) result.address = DataAddress( kind=DataAddress.ADDRESS_PRIVATE, street=(extract("adresselinje1_privatadresse"), extract("adresselinje2_privatadresse")), zip=extract("poststednr_privatadresse"), city=extract("poststednavn_privatadresse")) # Contact information and jobs # FIXME: We do not have anything more intelligent for priorities priorities = dict() for sub in element.getiterator(): if sub.tag in ( "bilag", "gjest", "tils", ): emp = self._make_employment(sub) result.add_employment(emp) elif sub.tag in ( "komm", "arbtlf", ): for contact in self._make_contact(sub, priorities): result.add_contact(contact) # od # od # Reservation rules. Roughly, all employees are not reserved, unless # they say otherwise. Everyone else *is* reserved, unless they # explicitly allow publication in catalogues. has_active = result.has_active_employments() if has_active: to_reserve = False for resv in element.findall("res"): if (resv.get("katalogkode") == "ELKAT" and resv.get("felttypekode") not in ("PRIVADR", "PRIVTLF") and resv.get("resnivakode") != "SAMTYKKE"): to_reserve = True else: to_reserve = True for resv in element.findall("res"): if (resv.get("katalogkode") == "ELKAT" and resv.get("felttypekode") not in ("PRIVADR", "PRIVTLF")): to_reserve = resv.get("resnivakode") != "SAMTYKKE" result.reserved = to_reserve if (element.get("fakultetnr_for_lonnsslip") and element.get("instituttnr_for_lonnsslip") and element.get("gruppenr_for_lonnsslip")): result.primary_ou = (cereconf.DEFAULT_INSTITUSJONSNR, extract("fakultetnr_for_lonnsslip"), extract("instituttnr_for_lonnsslip"), extract("gruppenr_for_lonnsslip")) if not (result.get_name(result.NAME_FIRST) and result.get_name(result.NAME_LAST)): raise AssertionError("Missing name for %s" % list(result.iterids())) return result
def process_person_callback(person_info): """Called when we have fetched all data on a person from the xml file. Updates/inserts name, address and affiliation information.""" global no_name try: fnr = fodselsnr.personnr_ok("%06d%05d" % ( int(person_info['fodselsdato']), int(person_info['personnr']))) fnr = fodselsnr.personnr_ok(fnr) logger.info("Processing %s", fnr) (year, mon, day) = fodselsnr.fodt_dato(fnr) if (year < 1970 and getattr(cereconf, "ENABLE_MKTIME_WORKAROUND", 0) == 1): # Seems to be a bug in time.mktime on some machines year = 1970 except fodselsnr.InvalidFnrError: logger.warn(u"Ugyldig f�dselsnr for: %s", person_info['fodselsdato']) return gender = co.gender_male if(fodselsnr.er_kvinne(fnr)): gender = co.gender_female etternavn = fornavn = None studentnr = None affiliations = [] address_info = None aktiv_sted = [] # Iterate over all person_info entries and extract relevant data for dta_type in person_info.keys(): x = person_info[dta_type] p = x[0] if isinstance(p, str): continue if dta_type not in ('tilbud', 'eksamen', 'evu'): if 'studentnr_tildelt' in p: studentnr = p['studentnr_tildelt'] else: logger.info("\n%s mangler studentnr!", fnr) # Get name if dta_type in ('aktiv', 'tilbud', 'evu', 'privatist_studieprogram', ): etternavn = p['etternavn'] fornavn = p['fornavn'] # Get address if address_info is None: if dta_type in ('aktiv', 'privatist_studieprogram', ): address_info = _ext_address_info( p, 'adrlin1_semadr', 'adrlin2_semadr', 'adrlin3_semadr', 'postnr_semadr', 'adresseland_semadr') if address_info is None: address_info = _ext_address_info( p, 'adrlin1_hjemsted', 'adrlin2_hjemsted', 'adrlin3_hjemsted', 'postnr_hjemsted', 'adresseland_hjemsted') elif dta_type in ('evu',): address_info = _ext_address_info( p, 'adrlin1_hjem', 'adrlin2_hjem', 'adrlin3_hjem', 'postnr_hjem', 'adresseland_hjem') if address_info is None: address_info = _ext_address_info( p, 'adrlin1_hjemsted', 'adrlin2_hjemsted', 'adrlin3_hjemsted', 'postnr_hjemsted', 'adresseland_hjemsted') elif dta_type in ('tilbud',): address_info = _ext_address_info( p, 'adrlin1_kontakt', 'adrlin2_kontakt', 'adrlin3_kontakt', 'postnr_kontakt', 'adresseland_kontakt') # Get affiliations # Lots of changes here compared to import_FS.py @ uio # TODO: split import_FS into a common part and organization spesific # parts if dta_type in ('aktiv', ): for row in x: # aktiv_sted is necessary in order to avoid different # affiliation statuses to a same 'stedkode' to be overwritten # e.i. if a person has both affiliations status 'tilbud' and # aktive to a single stedkode we want to register the status # 'aktive' in cerebrum if studieprog2sko[row['studieprogramkode']] is not None: aktiv_sted.append( int(studieprog2sko[row['studieprogramkode']])) _process_affiliation( co.affiliation_student, co.affiliation_status_student_aktiv, affiliations, studieprog2sko[row['studieprogramkode']]) elif dta_type in ('evu',): for row in x: _process_affiliation( co.affiliation_student, co.affiliation_status_student_evu, affiliations, _get_sko(p, 'faknr_adm_ansvar', 'instituttnr_adm_ansvar', 'gruppenr_adm_ansvar')) elif dta_type in ('privatist_studieprogram', ): for row in x: _process_affiliation( co.affiliation_student, co.affiliation_status_student_privatist, affiliations, studieprog2sko[row['studieprogramkode']]) elif dta_type in ('tilbud', ): for row in x: subtype = co.affiliation_status_student_tilbud if studieprog2sko[row['studieprogramkode']] in aktiv_sted: subtype = co.affiliation_status_student_aktiv _process_affiliation(co.affiliation_student, subtype, affiliations, studieprog2sko[row['studieprogramkode']]) if etternavn is None: logger.info("Ikke noe navn p� %s", fnr) no_name += 1 return # TODO: If the person already exist and has conflicting data from # another source-system, some mechanism is needed to determine the # superior setting. fsids = [(co.externalid_fodselsnr, fnr)] if studentnr is not None: fsids.append((co.externalid_studentnr, studentnr)) new_person = Factory.get('Person')(db) try: new_person.find_by_external_ids(*fsids) except Errors.NotFoundError: pass except Errors.TooManyRowsError, e: logger.error("Trying to find studentnr %s, getting several persons: %s", studentnr, e) return
def next_object(self, element): def get_value(element_value): return ensure_unicode(element_value, self.encoding) def extract(element_attr): return get_value(element.get(element_attr, "")) result = HRDataPerson() # Pull out all names self._register_names(result, element) # Pull out fnr tmp = "%02d%02d%02d%05d" % tuple([int(element.get(x)) for x in ("fodtdag", "fodtmnd", "fodtar", "personnr")]) fnr = fodselsnr.personnr_ok(tmp) result.add_id(result.NO_SSN, fnr) # Since LT does not provide birth date directly, we extract it from fnr result.birth_date = Date(*fodselsnr.fodt_dato(fnr)) # ... and gender if fodselsnr.er_mann(fnr): result.gender = result.GENDER_MALE else: result.gender = result.GENDER_FEMALE # fi # Register address # extract = lambda y: ensure_unicode(element.get(y, ""), self.encoding) result.address = DataAddress( kind=DataAddress.ADDRESS_PRIVATE, street=(extract("adresselinje1_privatadresse"), extract("adresselinje2_privatadresse")), zip=extract("poststednr_privatadresse"), city=extract("poststednavn_privatadresse")) # Contact information and jobs # FIXME: We do not have anything more intelligent for priorities priorities = dict() for sub in element.getiterator(): if sub.tag in ("bilag", "gjest", "tils",): emp = self._make_employment(sub) result.add_employment(emp) elif sub.tag in ("komm", "arbtlf",): for contact in self._make_contact(sub, priorities): result.add_contact(contact) # od # od # Reservation rules. Roughly, all employees are not reserved, unless # they say otherwise. Everyone else *is* reserved, unless they # explicitly allow publication in catalogues. has_active = result.has_active_employments() if has_active: to_reserve = False for resv in element.findall("res"): if (resv.get("katalogkode") == "ELKAT" and resv.get("felttypekode") not in ("PRIVADR", "PRIVTLF") and resv.get("resnivakode") != "SAMTYKKE"): to_reserve = True else: to_reserve = True for resv in element.findall("res"): if (resv.get("katalogkode") == "ELKAT" and resv.get("felttypekode") not in ("PRIVADR", "PRIVTLF")): to_reserve = resv.get("resnivakode") != "SAMTYKKE" result.reserved = to_reserve if (element.get("fakultetnr_for_lonnsslip") and element.get("instituttnr_for_lonnsslip") and element.get("gruppenr_for_lonnsslip")): result.primary_ou = (cereconf.DEFAULT_INSTITUSJONSNR, extract("fakultetnr_for_lonnsslip"), extract("instituttnr_for_lonnsslip"), extract("gruppenr_for_lonnsslip")) if not (result.get_name(result.NAME_FIRST) and result.get_name(result.NAME_LAST)): raise AssertionError("Missing name for %s" % list(result.iterids())) return result
def process_person_callback(person_info): """Called when we have fetched all data on a person from the xml file. Updates/inserts name, address and affiliation information.""" global no_name try: fnr = fodselsnr.personnr_ok("%06d%05d" % (int(person_info['fodselsdato']), int(person_info['personnr']))) fnr = fodselsnr.personnr_ok(fnr) logger.info("Process %s " % (fnr)) (year, mon, day) = fodselsnr.fodt_dato(fnr) if (year < 1970 and getattr(cereconf, "ENABLE_MKTIME_WORKAROUND", 0) == 1): # Seems to be a bug in time.mktime on some machines year = 1970 except fodselsnr.InvalidFnrError: logger.warn("Ugyldig fødselsnr: %s" % fnr) return gender = co.gender_male if(fodselsnr.er_kvinne(fnr)): gender = co.gender_female etternavn = fornavn = None studentnr = None affiliations = [] address_info = None aktiv_sted = [] # Iterate over all person_info entries and extract relevant data if person_info.has_key('aktiv'): for row in person_info['aktiv']: if studieprog2sko[row['studieprogramkode']] is not None: aktiv_sted.append(int(studieprog2sko[row['studieprogramkode']])) for dta_type in person_info.keys(): x = person_info[dta_type] p = x[0] if isinstance(p, str): continue # Get name if dta_type in ('fagperson', 'evu', 'aktiv'): etternavn = p['etternavn'] fornavn = p['fornavn'] if p.has_key('studentnr_tildelt'): studentnr = p['studentnr_tildelt'] # Get affiliations if dta_type in ('fagperson',): _process_affiliation(co.affiliation_tilknyttet, co.affiliation_status_tilknyttet_fagperson, affiliations, _get_sko(p, 'faknr', 'instituttnr', 'gruppenr', 'institusjonsnr')) elif dta_type in ('aktiv', ): for row in x: # aktiv_sted is necessary in order to avoid different affiliation statuses # to a same 'stedkode' to be overwritten # e.i. if a person has both affiliations status 'evu' and # aktive to a single stedkode we want to register the status 'aktive' # in cerebrum if studieprog2sko[row['studieprogramkode']] is not None: aktiv_sted.append(int(studieprog2sko[row['studieprogramkode']])) _process_affiliation(co.affiliation_student, co.affiliation_status_student_aktiv, affiliations, studieprog2sko[row['studieprogramkode']]) elif dta_type in ('evu',): subtype = co.affiliation_status_student_evu if studieprog2sko[row['studieprogramkode']] in aktiv_sted: subtype = co.affiliation_status_student_aktiv _process_affiliation(co.affiliation_student, subtype, affiliations, studieprog2sko[row['studieprogramkode']]) if etternavn is None: logger.debug("Ikke noe navn på %s" % fnr) no_name += 1 return # TODO: If the person already exist and has conflicting data from # another source-system, some mechanism is needed to determine the # superior setting. new_person = Factory.get('Person')(db) if fnr2person_id.has_key(fnr): new_person.find(fnr2person_id[fnr]) new_person.populate(mx.DateTime.Date(year, mon, day), gender) new_person.affect_names(co.system_fs, co.name_first, co.name_last) new_person.populate_name(co.name_first, fornavn) new_person.populate_name(co.name_last, etternavn) if studentnr is not None: new_person.affect_external_id(co.system_fs, co.externalid_fodselsnr, co.externalid_studentnr) new_person.populate_external_id(co.system_fs, co.externalid_studentnr, studentnr) else: new_person.affect_external_id(co.system_fs, co.externalid_fodselsnr) new_person.populate_external_id(co.system_fs, co.externalid_fodselsnr, fnr) ad_post, ad_post_private, ad_street = _calc_address(person_info) for address_info, ad_const in ((ad_post, co.address_post), (ad_post_private, co.address_post_private), (ad_street, co.address_street)): # TBD: Skal vi slette evt. eksisterende adresse v/None? if address_info is not None: logger.debug("Populating address...") new_person.populate_address(co.system_fs, ad_const, **address_info) # if this is a new Person, there is no entity_id assigned to it # until written to the database. op = new_person.write_db() for a in filter_affiliations(affiliations): ou, aff, aff_status = a new_person.populate_affiliation(co.system_fs, ou, aff, aff_status) if include_delete: key_a = "%s:%s:%s" % (new_person.entity_id,ou,int(aff)) if old_aff.has_key(key_a): old_aff[key_a] = False register_cellphone(new_person, person_info) op2 = new_person.write_db() if op is None and op2 is None: logger.info("**** EQUAL ****") elif op == True: logger.info("**** NEW ****") else: logger.info("**** UPDATE ****") register_fagomrade(new_person, person_info) # Reservations if gen_groups: should_add = False if person_info.has_key('nettpubl'): for row in person_info['nettpubl']: if row.get('akseptansetypekode', "") == "NETTPUBL" and row.get('status_svar', "") == "J": should_add = True if should_add: # The student has explicitly given us permission to be # published in the directory. _add_res(new_person.entity_id) else: # The student either hasn't registered an answer to # the "Can we publish info about you in the directory" # question at all, or has given an explicit "I don't # want to appear in the directory" answer. _rem_res(new_person.entity_id) db.commit()
def _process_person(self, person): db = self.db const = self.const paga_nr = int(person['ansattnr']) logger.info("Processing paga_id=%r", paga_nr) try: birthdate = parse_date(person['fodselsdato']) except ValueError as e: raise SkipPerson("Invalid birth date (%s)" % (e, )) if person['kjonn'] == 'M': gender = const.gender_male else: gender = const.gender_female fnr = person['fnr'] if fnr and fnr[6:11] != '00000': try: fodselsnr.personnr_ok(fnr) except Exception as e: raise SkipPerson("Invalid fnr (%s)" % (e, )) gender_chk = const.gender_male if fodselsnr.er_kvinne(fnr): gender_chk = const.gender_female if gender_chk != gender: raise SkipPerson("Inconsistent gender (gender=%r, ssn=%r)" % (gender, gender_chk)) fnr_date = datetime.date(*(fodselsnr.fodt_dato(fnr))) if not cmp_birthdates(birthdate, fnr_date): raise SkipPerson("Inconsistent birth date (date=%r, ssn=%r)" % (birthdate, fnr_date)) # Passport data national_id_type = person['edag_id_type'] origin_country = person['country'] if not origin_country: origin_country = 'NO' if person['edag_id_nr']: # generate external id on the form: # <2-char national id>-<national_id> national_id_val = '%s-%s' % (origin_country, person['edag_id_nr']) else: national_id_val = None # Abort early if there are no valid identifiers from the source system: if not any(( fnr and fnr[6:11] != '00000', national_id_type == 'passnummer' and national_id_val, )): # TODO: Wouldn't it be enough with ansattnr? raise SkipPerson("No valid identifier (fnr, passnummer)") new_person = Factory.get('Person')(db) identifiers = [(const.externalid_paga_ansattnr, paga_nr), (const.externalid_fodselsnr, fnr)] if national_id_type == 'passnummer' and national_id_val: identifiers.append((const.externalid_pass_number, national_id_val)) for id_type, id_value in identifiers: if _populate_existing(new_person, id_type, id_value): break if not person.get('fornavn', '').strip(): raise SkipPerson("Missing first name") if not person.get('etternavn', '').strip(): raise SkipPerson("Missing last name") new_person.populate(mx.DateTime.DateFrom(birthdate), gender) new_person.affect_names(const.system_paga, const.name_first, const.name_last) new_person.affect_external_id(const.system_paga, const.externalid_fodselsnr, const.externalid_paga_ansattnr, const.externalid_pass_number) new_person.populate_name(const.name_first, person['fornavn']) new_person.populate_name(const.name_last, person['etternavn']) if fnr and fnr[6:11] != '00000': # do not import external id where external_id type is fnr and # fnr[6-11] =='00000' new_person.populate_external_id(const.system_paga, const.externalid_fodselsnr, fnr) if national_id_type == 'passnummer' and national_id_val: new_person.populate_external_id(const.system_paga, const.externalid_pass_number, national_id_val) new_person.populate_external_id(const.system_paga, const.externalid_paga_ansattnr, paga_nr) # If it's a new person, we need to call write_db() to have an entity_id # assigned to it. op = new_person.write_db() if person.get('tittel_personlig'): new_person.add_name_with_language( name_variant=const.personal_title, name_language=const.language_nb, name=person['tittel_personlig']) # work_title is set by _determine_affiliations affiliations = _determine_affiliations(self.db, self.const, new_person, person) new_person.populate_affiliation(const.system_paga) if 'fakultetnr_for_lonnsslip' in person: sted = get_sted(db, person['fakultetnr_for_lonnsslip'], person['instituttnr_for_lonnsslip'], person['gruppenr_for_lonnsslip']) if sted is not None: if sted['addr_street'] is not None: new_person.populate_address(const.system_paga, type=const.address_street, **sted['addr_street']) if sted['addr_post'] is not None: new_person.populate_address(const.system_paga, type=const.address_post, **sted['addr_post']) if 'lokasjon' in person: logger.debug( 'Populating paga_id=%r location address with ' 'source=%s, type=%s, text=%r', paga_nr, const.system_paga, const.address_location, person['lokasjon']) new_person.populate_address(source_system=const.system_paga, type=const.address_location, address_text=person['lokasjon']) else: logger.warning("No location address for paga_id=%r", paga_nr) for k, v in affiliations.items(): ou_id, aff, aff_stat = v new_person.populate_affiliation(const.system_paga, ou_id, int(aff), int(aff_stat)) self.old_affs.discard(k) c_prefs = {} new_person.populate_contact_info(const.system_paga) for c_type, value in determine_contact(const, person): c_type = int(c_type) pref = c_prefs.get(c_type, 0) new_person.populate_contact_info(const.system_paga, c_type, value, pref) c_prefs[c_type] = pref + 1 # # Also add personal/home street address if it exists in the import file # priv_addr = (person.get('adresse'), person.get('postnr'), person.get('poststed')) if any(priv_addr): logger.debug("Setting additional home address: %s %s %s", priv_addr[0], priv_addr[1], priv_addr[2]) # TODO: Address country? Should probably use const.human2constant() # country_code = new_person.get_country_code(origin_country) # TODO: Is there really a guaranteed connection between passport # origin country and home address? new_person.populate_address(const.system_paga, const.address_post_private, priv_addr[0], None, priv_addr[1], priv_addr[2], None) op2 = new_person.write_db() set_person_spreads(self.db, self.const, new_person, person) if op is None and op2 is None: logger.info("EQUAL: No change to person with paga_id=%r", paga_nr) elif op: logger.info("NEW: Created new person with paga_id=%r", paga_nr) else: logger.info("UPDATE: Updated person with paga_id=%r (%s, %s)", paga_nr, op, op2)
def process_person_callback(person_info): """Called when we have fetched all data on a person from the xml file. Updates/inserts name, address and affiliation information.""" global no_name try: fnr = fodselsnr.personnr_ok( "%06d%05d" % (int(person_info['fodselsdato']), int(person_info['personnr']))) fnr = fodselsnr.personnr_ok(fnr) logger.info("Process %s " % (fnr)) (year, mon, day) = fodselsnr.fodt_dato(fnr) if (year < 1970 and getattr(cereconf, "ENABLE_MKTIME_WORKAROUND", 0) == 1): # Seems to be a bug in time.mktime on some machines year = 1970 except fodselsnr.InvalidFnrError: logger.warn("Ugyldig fødselsnr: %s" % fnr) return gender = co.gender_male if (fodselsnr.er_kvinne(fnr)): gender = co.gender_female etternavn = fornavn = None studentnr = None affiliations = [] address_info = None aktiv_sted = [] for dta_type in person_info.keys(): x = person_info[dta_type] p = x[0] if isinstance(p, str): continue # Get name if dta_type in ('fagperson', 'evu', 'aktiv'): etternavn = p['etternavn'] fornavn = p['fornavn'] if p.has_key('studentnr_tildelt'): studentnr = '%06d' % int(p['studentnr_tildelt']) if etternavn is None: logger.debug("Ikke noe navn på %s" % fnr) no_name += 1 return new_person = Factory.get('Person')(db) if fnr2person_id.has_key(fnr): new_person.find(fnr2person_id[fnr]) new_person.populate(mx.DateTime.Date(year, mon, day), gender) new_person.affect_names(co.system_fs, co.name_first, co.name_last) new_person.populate_name(co.name_first, fornavn) new_person.populate_name(co.name_last, etternavn) if studentnr is not None: new_person.affect_external_id(co.system_fs, co.externalid_fodselsnr, co.externalid_studentnr) new_person.populate_external_id(co.system_fs, co.externalid_studentnr, studentnr) logger.debug("Studentnr is %s", studentnr) else: new_person.affect_external_id(co.system_fs, co.externalid_fodselsnr) new_person.populate_external_id(co.system_fs, co.externalid_fodselsnr, fnr) ad_post, ad_post_private, ad_street = _calc_address(person_info) for address_info, ad_const in ((ad_post, co.address_post), (ad_post_private, co.address_post_private), (ad_street, co.address_street)): # TBD: Skal vi slette evt. eksisterende adresse v/None? if address_info is not None: logger.debug("Populating address...") new_person.populate_address(co.system_fs, ad_const, **address_info) # if this is a new Person, there is no entity_id assigned to it # until written to the database. op = new_person.write_db() # Iterate over all person_info entries and extract relevant data if person_info.has_key('aktiv'): for row in person_info['aktiv']: try: if studieprog2sko[row['studieprogramkode']] is not None: aktiv_sted.append( int(studieprog2sko[row['studieprogramkode']])) logger.debug("App2akrivts") except KeyError: logger.warn( 'App2akrivts: Person id %d har stud.prog.kode %s som ikke eksisterer.', new_person.entity_id, row['studieprogramkode']) for dta_type in person_info.keys(): # Get affiliations if dta_type in ('fagperson', ): _process_affiliation( co.affiliation_tilknyttet, co.affiliation_status_tilknyttet_fagperson, affiliations, _get_sko(p, 'faknr', 'instituttnr', 'gruppenr', 'institusjonsnr')) elif dta_type in ('aktiv', ): for row in x: # aktiv_sted is necessary in order to avoid different affiliation statuses # to a same 'stedkode' to be overwritten # e.i. if a person has both affiliations status 'evu' and # aktive to a single stedkode we want to register the status 'aktive' # in cerebrum try: if studieprog2sko[row['studieprogramkode']] is not None: aktiv_sted.append( int(studieprog2sko[row['studieprogramkode']])) _process_affiliation( co.affiliation_student, co.affiliation_status_student_aktiv, affiliations, studieprog2sko[row['studieprogramkode']]) except KeyError: logger.warn( 'AKTIV: Person id %d har stud.prog.kode %s som ikke eksisterer.', new_person.entity_id, row['studieprogramkode']) elif dta_type in ('evu', ): subtype = co.affiliation_status_student_evu try: if studieprog2sko[row['studieprogramkode']] in aktiv_sted: subtype = co.affiliation_status_student_aktiv _process_affiliation(co.affiliation_student, subtype, affiliations, studieprog2sko[row['studieprogramkode']]) except KeyError: logger.warn( 'EVU: Person id %d har stud.prog.kode %s som ikke eksisterer.', new_person.entity_id, row['studieprogramkode']) for a in filter_affiliations(affiliations): ou, aff, aff_status = a new_person.populate_affiliation(co.system_fs, ou, aff, aff_status) if include_delete: key_a = "%s:%s:%s" % (new_person.entity_id, ou, int(aff)) if old_aff.has_key(key_a): old_aff[key_a] = False register_contact(new_person, person_info) op2 = new_person.write_db() if op is None and op2 is None: logger.info("**** EQUAL ****") elif op == True: logger.info("**** NEW ****") else: logger.info("**** UPDATE ****") # Reservations if gen_groups: should_add = False for dta_type in person_info.keys(): p = person_info[dta_type][0] if isinstance(p, str): continue # Presence of 'fagperson' elements for a person should not # affect that person's reservation status. if dta_type in ('fagperson', ): continue # We only fetch the column in these queries if dta_type not in ('evu'): continue # If 'status_reserv_nettpubl' == "N": add to group if p.get('status_reserv_nettpubl', "") == "N": should_add = True else: should_add = False if should_add: # The student has explicitly given us permission to be # published in the directory. _add_res(new_person.entity_id) else: # The student either hasn't registered an answer to # the "Can we publish info about you in the directory" # question at all, or has given an explicit "I don't # want to appear in the directory" answer. _rem_res(new_person.entity_id) db.commit()
def fetch_data(drgrad_file, fritak_kopiavg_file, sysname, person_file, autostud): """Finner alle personer som rammes av kvoteordningen ved å: - finne alle som har en student-affiliation (0.1) - ta bort ansatte (1.2.1) - ta bort dr-grads stipendiater (1.2.2) I.e. vi fjerner alle som har fullstendig fritak fra ordningen. De som kun har fritak fra kopiavgiften er ikke fjernet. Finner alle gruppe-medlemskap, enten via person eller account for de som rammes av ordningen. Finner fritak fra kopiavgift (1.2.3 og 1.2.4) """ logger.debug("Prefetching data") betaling_fritak = get_bet_fritak_utv_data(sysname, person_file) logger.debug("Fritak for: %r", betaling_fritak) # Finn alle som skal rammes av kvoteregimet quota_victim = {} for pid in get_students(): quota_victim[pid] = True # Ta bort de som ikke har konto account = Account.Account(db) account_id2pid = {} has_account = {} for row in account.list_accounts_by_type(fetchall=False): account_id2pid[int(row['account_id'])] = int(row['person_id']) has_account[int(row['person_id'])] = True for p_id in quota_victim.keys(): if p_id not in has_account: del (quota_victim[p_id]) logger.debug("after removing non-account people: %i" % len(quota_victim)) # Ansatte har fritak for row in pe.list_affiliations(affiliation=co.affiliation_ansatt, status=( co.affiliation_status_ansatt_bil, co.affiliation_status_ansatt_vit, co.affiliation_status_ansatt_tekadm, ), source_system=co.system_sap, include_deleted=False, fetchall=False): if int(row['person_id']) in quota_victim: del (quota_victim[int(row['person_id'])]) logger.debug("removed employees: %i" % len(quota_victim)) # Alle personer som har disse typer tilknyttet affiliation skal ha fritak for row in pe.list_affiliations( affiliation=co.affiliation_tilknyttet, status=( co.affiliation_tilknyttet_bilag, co.affiliation_tilknyttet_ekst_forsker, co.affiliation_tilknyttet_ekst_partner, co.affiliation_tilknyttet_ekst_stip, co.affiliation_tilknyttet_emeritus, co.affiliation_tilknyttet_gjesteforsker, co.affiliation_tilknyttet_innkjoper, ), source_system=co.system_sap, include_deleted=False, fetchall=False): if int(row['person_id']) in quota_victim: del (quota_victim[int(row['person_id'])]) logger.debug("removed tilknyttet people: %i" % len(quota_victim)) # Mappe fødselsnummer til person-id fnr2pid = {} for p in pe.search_external_ids(source_system=co.system_fs, id_type=co.externalid_fodselsnr, fetchall=False): fnr2pid[p['external_id']] = int(p['entity_id']) # Dr.grads studenter har fritak for row in GeneralDataParser(drgrad_file, "drgrad"): fnr = fodselsnr.personnr_ok( "%06d%05d" % (int(row['fodselsdato']), int(row['personnr']))) pid = fnr2pid.get(fnr, None) if not pid: continue if pid in quota_victim: del (quota_victim[pid]) logger.debug("removed drgrad: %i" % len(quota_victim)) # map person-id til gruppemedlemskap. Hvis gruppe-medlemet er av # typen konto henter vi ut owner_id. person_id_member = {} group = Group.Group(db) count = [0, 0] groups = autostud.pc.group_defs.keys() logger.debug("Finding members in %s" % groups) for g in groups: group.clear() group.find(g) for m in set([ int(row["member_id"]) for row in group.search_members(group_id=group.entity_id, indirect_members=True, member_type=co.entity_account) ]): if m in account_id2pid: person_id_member.setdefault(account_id2pid[m], []).append(g) count[0] += 1 else: person_id_member.setdefault(m, []).append(g) count[1] += 1 logger.debug("memberships: persons:%i, p_m=%i, a_m=%i", len(person_id_member), count[1], count[0]) # Fetch any affiliations used as select criteria person_id_affs = {} for sm in autostud.pc.select_tool.select_map_defs.values(): if not isinstance(sm, AutoStud.Select.SelectMapPersonAffiliation): continue for aff_attrs in sm._select_map.keys(): affiliation = aff_attrs[0] for row in pe.list_affiliations(affiliation=affiliation, include_deleted=False, fetchall=False): person_id_affs.setdefault(int(row['person_id']), []).append( (int(row['ou_id']), int(row['affiliation']), int(row['status']))) # fritak fra selve kopiavgiften (1.2.3 og 1.2.4) kopiavgift_fritak = {} for row in GeneralDataParser(fritak_kopiavg_file, "betfritak"): fnr = fodselsnr.personnr_ok( "%06d%05d" % (int(row['fodselsdato']), int(row['personnr']))) pid = fnr2pid.get(fnr, None) if not pid: continue kopiavgift_fritak[pid] = True logger.debug("%i personer har kopiavgiftsfritak" % len(kopiavgift_fritak)) logger.debug("Prefetch returned %i students" % len(quota_victim)) n = 0 for pid in betaling_fritak.keys(): if pid in quota_victim: n += 1 logger.debug("%i av disse har betaling_fritak" % n) return (fnr2pid, quota_victim, person_id_member, person_id_affs, kopiavgift_fritak, betaling_fritak)
def process_person_callback(person_info): """Called when we have fetched all data on a person from the xml file. Updates/inserts name, address and affiliation information.""" global no_name try: fnr = fodselsnr.personnr_ok( "%06d%05d" % (int(person_info['fodselsdato']), int(person_info['personnr']))) fnr = fodselsnr.personnr_ok(fnr) logger.info("Process %s " % (fnr)) (year, mon, day) = fodselsnr.fodt_dato(fnr) if (year < 1970 and getattr(cereconf, "ENABLE_MKTIME_WORKAROUND", 0) == 1): # Seems to be a bug in time.mktime on some machines year = 1970 except fodselsnr.InvalidFnrError: logger.warn("Ugyldig fødselsnr: %s" % fnr) return gender = co.gender_male if (fodselsnr.er_kvinne(fnr)): gender = co.gender_female etternavn = fornavn = None studentnr = None affiliations = [] address_info = None aktiv_sted = [] # Iterate over all person_info entries and extract relevant data if person_info.has_key('aktiv'): for row in person_info['aktiv']: if studieprog2sko[row['studieprogramkode']] is not None: aktiv_sted.append(int( studieprog2sko[row['studieprogramkode']])) logger.debug("App2akrivts") for dta_type in person_info.keys(): x = person_info[dta_type] p = x[0] if isinstance(p, str): continue # Get name if dta_type in ('fagperson', 'evu', 'aktiv'): etternavn = p['etternavn'] fornavn = p['fornavn'] if p.has_key('studentnr_tildelt'): studentnr = p['studentnr_tildelt'] # Get affiliations if dta_type in ('fagperson', ): _process_affiliation( co.affiliation_tilknyttet, co.affiliation_status_tilknyttet_fagperson, affiliations, _get_sko(p, 'faknr', 'instituttnr', 'gruppenr', 'institusjonsnr')) elif dta_type in ('aktiv', ): for row in x: # aktiv_sted is necessary in order to avoid different affiliation statuses # to a same 'stedkode' to be overwritten # e.i. if a person has both affiliations status 'evu' and # aktive to a single stedkode we want to register the status 'aktive' # in cerebrum if studieprog2sko[row['studieprogramkode']] is not None: aktiv_sted.append( int(studieprog2sko[row['studieprogramkode']])) _process_affiliation( co.affiliation_student, co.affiliation_status_student_aktiv, affiliations, studieprog2sko[row['studieprogramkode']]) elif dta_type in ('evu', ): subtype = co.affiliation_status_student_evu if studieprog2sko[row['studieprogramkode']] in aktiv_sted: subtype = co.affiliation_status_student_aktiv _process_affiliation(co.affiliation_student, subtype, affiliations, studieprog2sko[row['studieprogramkode']]) if etternavn is None: logger.debug("Ikke noe navn på %s" % fnr) no_name += 1 return # TODO: If the person already exist and has conflicting data from # another source-system, some mechanism is needed to determine the # superior setting. new_person = Factory.get('Person')(db) if fnr2person_id.has_key(fnr): new_person.find(fnr2person_id[fnr]) new_person.populate(mx.DateTime.Date(year, mon, day), gender) new_person.affect_names(co.system_fs, co.name_first, co.name_last) new_person.populate_name(co.name_first, fornavn) new_person.populate_name(co.name_last, etternavn) if studentnr is not None: new_person.affect_external_id(co.system_fs, co.externalid_fodselsnr, co.externalid_studentnr) new_person.populate_external_id(co.system_fs, co.externalid_studentnr, studentnr) else: new_person.affect_external_id(co.system_fs, co.externalid_fodselsnr) new_person.populate_external_id(co.system_fs, co.externalid_fodselsnr, fnr) ad_post, ad_post_private, ad_street = _calc_address(person_info) for address_info, ad_const in ((ad_post, co.address_post), (ad_post_private, co.address_post_private), (ad_street, co.address_street)): # TBD: Skal vi slette evt. eksisterende adresse v/None? if address_info is not None: logger.debug("Populating address...") new_person.populate_address(co.system_fs, ad_const, **address_info) # if this is a new Person, there is no entity_id assigned to it # until written to the database. try: op = new_person.write_db() except Exception, e: logger.exception("write_db failed for person %s: %s", fnr, e) # Roll back in case of db exceptions: db.rollback() return
def import_person(persons, all_nodes): print("import person") global dryrun global cere_list global include_del logger.info( "database:%s" % cereconf.CEREBRUM_DATABASE_CONNECT_DATA['host']) for person in persons: ssn_not_valid = False person_to_be_processed = {} person = person.rstrip() person_list = person.split(";") logger.debug("--- Processing new person ---") try: # print "fnr:'%s'" % person_list[1] person_to_be_processed['noreduorgnin'] = person_list[3] person_to_be_processed['birth_day'] = person_list[0][0:2] person_to_be_processed['birth_month'] = person_list[0][2:4] person_to_be_processed['birth_year'] = person_list[0][4:6] # Make sure year contains 4 digits. if (person_to_be_processed['birth_year'] < 20): birth_year = "20%s" % (person_to_be_processed['birth_year']) else: birth_year = "19%s" % (person_to_be_processed['birth_year']) person_to_be_processed['birth_year'] = birth_year # print "birth_day:'%s'" % person_to_be_processed['birth_day'] # print "birth_month:'%s'" % person_to_be_processed['birth_month'] # print "birth_year:'%s'" % person_to_be_processed['birth_year'] except ValueError: logger.warning( "Empty Birthdate for person named:%s %s. Continue with " + "next person", person_list[1], person_list[2]) continue except IndexError: logger.warning("empty person file?") continue # Check if SSN is valid # print "current fnr:%s" % person_list[0] try: fodselsnr.personnr_ok(person_list[0]) person_to_be_processed['ssn'] = person_list[0] except fodselsnr.InvalidFnrError: logger.warning( "Empty or non-valid ssn %s for person:%s %s from :%s. " + "Continue with next person", person_list[0], person_list[1], person_list[2], person_list[4]) # ssn_not_valid = True # person_to_be_processed['ssn'] = '' continue # set person gender # gender = const.gender_male gender = const.gender_male if not ssn_not_valid: if fodselsnr.er_kvinne(person_to_be_processed['ssn']): gender = const.gender_female else: # Impossible to set gender. Return error message and set gender to # unknown logger.warning( "Impossible to set gender for person:%s %s. Using Unknown", person_list[1], person_list[2]) gender = const.gender_unknown # set gender person_to_be_processed['gender'] = gender # get person firstname person_to_be_processed['firstname'] = person_list[1] # print "firstname:%s" % person_to_be_processed['firstname'] # get person lastname person_to_be_processed['lastname'] = person_list[2] # print "lastname:%s" % person_to_be_processed['lastname'] if (person_to_be_processed['firstname'].isspace() or person_to_be_processed['lastname'].isspace()): # Firstname and/or lastname is made of whitespace ONLY. # generate error message and continue with NEXT person logger.warn( "missing first and/or lastname for person:%s. Person NOT " + "imported", person) continue # set correct encoding person_to_be_processed['firstname'] = decode_text( person_to_be_processed['firstname']) person_to_be_processed['lastname'] = decode_text( person_to_be_processed['lastname']) # # Finished building person_to_be_processed dict. # pp.pprint(person_to_be_processed) # # create person object db_person.clear() try: db_person.find_by_external_id(const.externalid_fodselsnr, person_to_be_processed['ssn']) logger.info("Ssn already in database. update person object") # existing_person = True except Errors.NotFoundError: logger.warning("Unknown ssn:%s, create new person object" % person_to_be_processed['ssn']) # Unable to find person with ssn in the database. pass # # Populate person object # try: db_person.populate( mx.DateTime.Date(int(person_to_be_processed['birth_year']), int(person_to_be_processed['birth_month']), int(person_to_be_processed['birth_day'])), int(person_to_be_processed['gender'])) except Errors.CerebrumError as m: # unable to populate person object. Return error message and # continue with next person logger.error("Person:%s population failed", person_to_be_processed['ssn'], m) continue # affect name and external id db_person.affect_names(const.system_flyt, const.name_first, const.name_last) # populate firstname, lastname and external id db_person.populate_name(const.name_first, person_to_be_processed['firstname']) db_person.populate_name(const.name_last, person_to_be_processed['lastname']) db_person.affect_external_id(const.system_flyt, const.externalid_fodselsnr) db_person.populate_external_id(const.system_flyt, const.externalid_fodselsnr, person_to_be_processed['ssn']) # In case this is a new person, we will need to write to DB before # we can continue. try: op = db_person.write_db() except db.IntegrityError as e: db_person.clear() db.rollback() logger.info("Error:%s - person not imported to BAS", e) continue # op = db_person.write_db() # Determine person affiliation and affiliation_status det_ou, det_affiliation = determine_affiliation(person_list, all_nodes) # logger.debug(" --- from determine affiliation, the following is # calculated ---") # pp.pprint(det_affiliation) for single_ou in det_ou: for single_aff in det_affiliation: new_aff = getattr(const, single_aff) new_aff_stat = getattr(const, det_affiliation[single_aff]) ou.clear() ou.find_stedkode(single_ou[0:2], single_ou[2:4], single_ou[4:6], cereconf.DEFAULT_INSTITUSJONSNR) logger.debug( "setting:: ou_id:%s, aff:%s, aff_stat:%s for person:%s", int(ou.entity_id), int(new_aff), int(new_aff_stat), db_person.entity_id) db_person.populate_affiliation(const.system_flyt, ou.entity_id, new_aff, new_aff_stat) k = "%s:%s:%s" % (db_person.entity_id, int(ou.entity_id), int(new_aff)) if include_del: if k in cere_list: cere_list[k] = False # store mobile for those that has it # contact = determine_contact(db_person) if len(person_list[13]) > 1: person_list[13] logger.debug("has mobile:%s" % person_list[13]) number = person_list[13] c_prefs = {} c_type = int(const.contact_mobile_phone) pref = c_prefs.get(c_type, 0) db_person.populate_contact_info(const.system_flyt, c_type, number, pref) pref = c_prefs[c_type] = pref = 1 else: logger.debug("No mobile registered for this user") op2 = db_person.write_db() if op is None and op2 is None: logger.info("**** EQUAL ****") elif op: logger.info("**** NEW ****") else: logger.info("**** UPDATE (%s:%s) ****" % (op, op2))
def create_sysx_person(db, sxp, update_affs, stats): """ Create or update person with sysx data. :param db: :param sxp: Person dict from ``SYSX`` :param update_affs: A set of current SYSX aff keys :param stats: A defaultdict with counts """ co = Factory.get('Constants')(db) add_sysx_id = fnr_found = False sysx_id = sxp['id'] fnr = sxp['fnr'] fornavn = sxp['fornavn'] etternavn = sxp['etternavn'] fullt_navn = "%s %s" % (fornavn, etternavn) if check_expired_sourcedata(sxp['expire_date']): logger.info("Skipping sysx_id=%r (%s), expire_date=%r", sysx_id, fullt_navn, sxp['expire_date']) stats['skipped'] += 1 return # check if approved. Ditch if not. if not sxp['approved']: logger.error("Skipping sysx_id=%r (%s), not approved", sysx_id, fullt_navn) stats['skipped'] += 1 return my_stedkode = Factory.get('OU')(db) pers_sysx = Factory.get('Person')(db) pers_fnr = Factory.get('Person')(db) try: pers_sysx.find_by_external_id(co.externalid_sys_x_id, sysx_id) except Errors.NotFoundError: add_sysx_id = True if fnr: try: fnr = fodselsnr.personnr_ok(fnr) except fodselsnr.InvalidFnrError: logger.error("Skipping sysx_id=%r (%s), invalid fnr", sysx_id, fullt_navn) stats['skipped'] += 1 return birth_date = datetime.date(*fodselsnr.fodt_dato(fnr)) if fodselsnr.er_kvinne(fnr): gender = co.gender_female else: gender = co.gender_male try: pers_fnr.find_by_external_id(co.externalid_fodselsnr, fnr) except Errors.NotFoundError: pass except Errors.TooManyRowsError as e: # This persons fnr has multiple rows in entity_external_id table # This is an error, person should not have not more than one entry. # Don't know which person object to use, return error message. logger.error( "Skipping sysx_id=%r (%s), matched multiple persons (%s)", sysx_id, fullt_navn, e) stats['skipped'] += 1 return else: if not add_sysx_id and (pers_fnr.entity_id != pers_sysx.entity_id): logger.error( "Skipping sysx_id=%r (%s), matched multiple persons (sysx " "id matched person_id=%r, sysx fnr matched person_id=%r)", sysx_id, fullt_navn, pers_sysx.entity_id, pers_fnr.entity_id) stats['skipped'] += 1 return fnr_found = True else: # foreigner without norwegian ssn, birth_date = sxp['birth_date'] if not birth_date: logger.error("sysx_id=%r (%s) is missing birth date", sysx_id, fullt_navn) if sxp['gender'] == 'M': gender = co.gender_male elif sxp['gender'] == 'F': gender = co.gender_female else: logger.error("Skipping sysx_id=%r (%s), invalid gender %r", sysx_id, fullt_navn, sxp['gender']) stats['skipped'] += 1 return logger.info("Processing sysx_id=%r (%s)", sysx_id, fullt_navn) if fnr_found: person = pers_fnr else: person = pers_sysx # person object located, populate... try: person.populate(mx.DateTime.DateFrom(birth_date), gender) except Errors.CerebrumError: logger.error("Skipping sysx_id=%r (%s), populate() failed", sysx_id, fullt_navn, exc_info=True) stats['skipped'] += 1 return person.affect_names(co.system_x, co.name_first, co.name_last, co.name_full) person.populate_name(co.name_first, fornavn) person.populate_name(co.name_last, etternavn) person.populate_name(co.name_full, fullt_navn) # add external ids if fnr: person.affect_external_id(co.system_x, co.externalid_fodselsnr, co.externalid_sys_x_id) person.populate_external_id(co.system_x, co.externalid_fodselsnr, fnr) logger.debug("Set NO_BIRTHNO for sysx_id=%r (%s)", sysx_id, fullt_navn) else: person.affect_external_id(co.system_x, co.externalid_sys_x_id) person.populate_external_id(co.system_x, co.externalid_sys_x_id, sysx_id) logger.debug("Set sysx_id for sysx_id=%r (%s)", sysx_id, fullt_navn) # setting affiliation and affiliation_status affiliation = co.PersonAffiliation(sxp['affiliation']) affiliation_status = co.PersonAffStatus(affiliation, sxp['affiliation_status']) # Assert that the affs are real int(affiliation), int(affiliation_status) # get ou_id of stedkode used fak = sxp['ou'][0:2] ins = sxp['ou'][2:4] avd = sxp['ou'][4:6] # KEB if fak == '0': logger.warning("sysx_id=%r (%s) has invalid sko=%r", sysx_id, fullt_navn, sxp['ou']) ins = avd = fak try: my_stedkode.find_stedkode(fak, ins, avd, cereconf.DEFAULT_INSTITUSJONSNR) except EntityExpiredError: logger.error("Skipping sysx_id=%r (%s), expired sko=%r", sysx_id, fullt_navn, sxp['ou']) stats['skipped'] += 1 return ou_id = int(my_stedkode.entity_id) # if this is a new Person, there is no entity_id assigned to it # until written to the database. op = person.write_db() # populate the person affiliation table person.populate_affiliation(co.system_x, ou_id, int(affiliation), int(affiliation_status)) # make sure we don't delete this aff when processing deleted affs aff_key = affiliation_key(person.entity_id, ou_id, affiliation) update_affs.discard(aff_key) op2 = person.write_db() logger.debug("OP codes: op=%s,op2=%s" % (op, op2)) if op is None and op2 is None: logger.info("**** EQUAL ****") stats['unchanged'] += 1 elif op is None and op2 is False: logger.info("**** AFF UPDATE ****") stats['updated'] += 1 elif op is True: logger.info("**** NEW ****") stats['added'] += 1 elif op is False: logger.info("**** UPDATE ****") stats['updated'] += 1 return
def approve_persons(self, input): """Let project owner and PAs approve more persons to their project. Sending this through Nettskjema is needed as we have no web GUI inside TSD in the beginning, and project admins need a way to approve people for their projects. """ # Find the project: pid = input['p_id'] ou.clear() ou.find_by_tsd_projectid(pid) logger.info('Approve persons for project: %s', pid) # Find the requestor pe = self._get_person() # The requestor must be owner or PA: if not list(pe.list_affiliations(person_id=pe.entity_id, affiliation=co.affiliation_project, status=(co.affiliation_status_project_owner, co.affiliation_status_project_admin), ou_id=ou.entity_id)): # TODO: Delete the JSON file? raise BadInputError("Person %s is not PA of project %s" % (pe.entity_id, pid)) # Update contact info for PA: self._update_person(pe, input) # Try to find and add the given person to the project pe2 = Factory.get('Person')(db) pre_approvals = set() for fnr in set(input['p_persons'].split()): for fnr1 in set(fnr.split(',')): try: fnr1 = fodselsnr.personnr_ok(fnr1) except fodselsnr.InvalidFnrError: logger.debug("Ignoring invalid fnr: %s", fnr1) continue # Find the person: try: pe2 = self._get_person(fnr1, create_nonexisting=False) except Errors.NotFoundError: logger.info("Person %s not found, added to pre approve list", fnr1) pre_approvals.add(fnr1) continue # Affiliate person to project: if not pe2.list_affiliations(person_id=pe2.entity_id, ou_id=ou.entity_id, affiliation=co.affiliation_project): logger.info("Approve person %s for project: %s", fnr1, pid) pe2.populate_affiliation(source_system=co.system_nettskjema, ou_id=ou.entity_id, affiliation=co.affiliation_project, status=co.affiliation_status_project_member) # Remove pending aff, if set. Note that this also removes any other # pending statuses, if we should create more of those in the future. pe2.delete_affiliation(ou_id=ou.entity_id, affiliation=co.affiliation_pending, source=co.system_nettskjema) pe2.write_db() # Find the member's project account, if it exist: accounts = self._get_project_account(pe2, ou) if not accounts: logger.info("No project account for %s, added to approve list", fnr1) pre_approvals.add(fnr1) continue ac.clear() ac.find(accounts[0]['account_id']) logger.info("Approving person %s PM to %s: %s ", fnr1, ac.account_name, pid) # Update the account types: ac.set_account_type(ou.entity_id, co.affiliation_project) # TODO: check if this works if the type is already set: ac.del_account_type(ou.entity_id, co.affiliation_pending) ac.del_account_type(ou.entity_id, co.affiliation_pending) # Remove quarantine: ac.delete_entity_quarantine(co.quarantine_not_approved) ac.write_db() # Promote posix: if ou.is_approved(): pu = Factory.get('PosixUser')(db) try: pu.find(ac.entity_id) except Errors.NotFoundError: uid = pu.get_free_uid() pu.populate(uid, None, None, co.posix_shell_bash, parent=ac, creator_id=systemaccount_id) logger.debug("Promote POSIX, UID: %s", ac.entity_id, uid) pu.write_db() # Stop if person already is PA or owner, can't have member-aff at # the same time: #if pe2.list_affiliations(person_id=pe2.entity_id, # affiliation=co.affiliation_project, # status=(co.affiliation_status_project_owner, # co.affiliation_status_project_admin), # ou_id=ou.entity_id): # logger.debug("Person is already owner or PA: %s", fnr1) # continue #pe2.populate_affiliation(source_system=co.system_nettskjema, # ou_id=ou.entity_id, # affiliation=co.affiliation_project, # status=co.affiliation_status_project_member) #pe2.write_db() # TODO: The person will get an account when successfully registered # with its name. This could have happened already, so might create # it now already? # Store those not processed for later approval: if pre_approvals: logger.debug("Remaining non-existing persons: %d", len(pre_approvals)) ou.add_pre_approved_persons(pre_approvals) ou.write_db() return True
def import_person(db, person, update_affs=None): """ Import a single person. :param person: A dict from py:func:`parse_person` :param update_affs: A set of affiliations from py:func:`load_sito_affiliations`. Affiliations imported by this function will be removed from the set. """ new_person = Factory.get('Person')(db) const = Factory.get('Constants')(db) if update_affs is None: update_affs = set() employee_id = person['employee_id'] logger.info("Processing employee_id=%r", employee_id) # # Validate person data # # Birthdate try: birthdate = parse_date(person['birthdate']) valid_birthdate = True except ValueError: logger.warning('Invalid birth date for employee_id=%r (%r)', person['employee_id'], person['birthdate']) valid_birthdate = False birthdate = None # SSN try: fodselsnr.personnr_ok(person['ssn']) valid_ssn = True except fodselsnr.InvalidFnrError: logger.warning("Empty SSN for employee_id=%r", person['employee_id']) valid_ssn = False # set person gender (checking both SSN and gender from sito input file) if valid_ssn: if fodselsnr.er_kvinne(person['ssn']): gender = const.gender_female else: gender = const.gender_male elif person['gender'] == 'Female': gender = const.gender_female elif person['gender'] == 'Male': gender = const.gender_male else: logger.warning('Unknown gender value for employee_id=%r (%r)', employee_id, person['gender']) gender = const.gender_unknown # Validate Birthdate against SSN if valid_ssn and valid_birthdate: ssndate = datetime.date(*fodselsnr.fodt_dato(person['ssn'])) if birthdate != ssndate: raise SkipPerson('inconsistent birth date and ssn (%r, %r)', birthdate, ssndate) elif valid_ssn and not valid_birthdate: logger.warning('Missing birth date for employee_id=%r, using date' ' from ssn', person['employee_id']) birthdate = datetime.date(*fodselsnr.fodt_dato(person['ssn'])) elif not valid_ssn and valid_birthdate: # person have birthdate but NOT ssn. Nothing to do here pass elif not valid_ssn and not valid_birthdate: # person does not have birthdate nor ssn. This person cannot be # built. SSN or Birthdate required. Return error message and # continue with NEXT person raise SkipPerson('missing ssn and birth date') # Check names if not person['first_name']: raise SkipPerson('Missing first name') if not person['last_name']: raise SkipPerson('Missing last name') if person['middle_name']: fname = person['first_name'] + ' ' + person['middle_name'] else: fname = person['first_name'] lname = person['last_name'] # # Get person object som DB if it exists # found = False new_person.clear() try: new_person.find_by_external_id(const.externalid_sito_ansattnr, employee_id) found = True except Errors.NotFoundError: # could not find person in DB based on ansattnr. if valid_ssn: # try to find person using ssn if ssn is valid try: new_person.clear() new_person.find_by_external_id(const.externalid_fodselsnr, person['ssn']) found = True except Errors.NotFoundError: pass if found: logger.info('Updating person object for employee_id=%r', employee_id) else: logger.info('Creating person object for employee_id=%r', employee_id) # # Populate the person object # new_person.populate(mx.DateTime.DateFrom(birthdate), gender) new_person.affect_names(const.system_sito, const.name_first, const.name_last, const.name_work_title) new_person.affect_external_id(const.system_sito, const.externalid_fodselsnr, const.externalid_sito_ansattnr) new_person.populate_name(const.name_first, fname) new_person.populate_name(const.name_last, lname) if person['title']: new_person.populate_name(const.name_work_title, person['title']) if valid_ssn: new_person.populate_external_id(const.system_sito, const.externalid_fodselsnr, person['ssn']) new_person.populate_external_id(const.system_sito, const.externalid_sito_ansattnr, employee_id) # intermediary write to get an entity_id if this is a new person. new_person.write_db() new_person.populate_affiliation(const.system_sito) new_person.populate_contact_info(const.system_sito) # set person affiliation for key, ou_id, aff, status in determine_affiliations( db, new_person.entity_id, person): logger.info("affiliation for employee_id=%r to ou_id=%r", employee_id, ou_id) new_person.populate_affiliation(const.system_sito, ou_id, int(aff), int(status)) # set this persons affiliation entry to False # this ensures that this persons affiliations will not be removed # when the clean_affiliation function is called after import person update_affs.discard(key) # get person work, cellular and home phone numbers c_prefs = {} for con, number in person['phone'].items(): c_type = int(const.human2constant(con, const.ContactInfo)) if c_type in c_prefs: pref = c_prefs[c_type] else: pref = 0 c_prefs[c_type] = 1 new_person.populate_contact_info(const.system_sito, c_type, number, pref) logger.debug("contact for employee_id=%r, system=%s, " "c_type=%s, number=%s, pref=%s", employee_id, const.system_sito, c_type, number, pref) new_person.write_db() return not found
def process_person_callback(person_info): """Called when we have fetched all data on a person from the xml file. Updates/inserts name, address and affiliation information.""" global no_name try: fnr = fodselsnr.personnr_ok("%06d%05d" % (int(person_info['fodselsdato']), int(person_info['personnr']))) fnr = fodselsnr.personnr_ok(fnr) logger.info("Process %s " % (fnr)) (year, mon, day) = fodselsnr.fodt_dato(fnr) if (year < 1970 and getattr(cereconf, "ENABLE_MKTIME_WORKAROUND", 0) == 1): # Seems to be a bug in time.mktime on some machines year = 1970 except fodselsnr.InvalidFnrError: logger.warn("Ugyldig fødselsnr: %s" % fnr) return gender = co.gender_male if(fodselsnr.er_kvinne(fnr)): gender = co.gender_female etternavn = fornavn = None studentnr = None affiliations = [] address_info = None aktiv_sted = [] # Iterate over all person_info entries and extract relevant data if person_info.has_key('aktiv'): for row in person_info['aktiv']: if studieprog2sko[row['studieprogramkode']] is not None: aktiv_sted.append(int(studieprog2sko[row['studieprogramkode']])) logger.debug("App2akrivts") for dta_type in person_info.keys(): x = person_info[dta_type] p = x[0] if isinstance(p, str): continue # Get name if dta_type in ('fagperson', 'evu', 'aktiv'): etternavn = p['etternavn'] fornavn = p['fornavn'] if p.has_key('studentnr_tildelt'): studentnr = p['studentnr_tildelt'] # Get affiliations if dta_type in ('fagperson',): _process_affiliation(co.affiliation_tilknyttet, co.affiliation_status_tilknyttet_fagperson, affiliations, _get_sko(p, 'faknr', 'instituttnr', 'gruppenr', 'institusjonsnr')) elif dta_type in ('aktiv', ): for row in x: # aktiv_sted is necessary in order to avoid different affiliation statuses # to a same 'stedkode' to be overwritten # e.i. if a person has both affiliations status 'evu' and # aktive to a single stedkode we want to register the status 'aktive' # in cerebrum if studieprog2sko[row['studieprogramkode']] is not None: aktiv_sted.append(int(studieprog2sko[row['studieprogramkode']])) _process_affiliation(co.affiliation_student, co.affiliation_status_student_aktiv, affiliations, studieprog2sko[row['studieprogramkode']]) elif dta_type in ('evu',): subtype = co.affiliation_status_student_evu if studieprog2sko[row['studieprogramkode']] in aktiv_sted: subtype = co.affiliation_status_student_aktiv _process_affiliation(co.affiliation_student, subtype, affiliations, studieprog2sko[row['studieprogramkode']]) if etternavn is None: logger.debug("Ikke noe navn på %s" % fnr) no_name += 1 return # TODO: If the person already exist and has conflicting data from # another source-system, some mechanism is needed to determine the # superior setting. new_person = Factory.get('Person')(db) if fnr2person_id.has_key(fnr): new_person.find(fnr2person_id[fnr]) new_person.populate(mx.DateTime.Date(year, mon, day), gender) new_person.affect_names(co.system_fs, co.name_first, co.name_last) new_person.populate_name(co.name_first, fornavn) new_person.populate_name(co.name_last, etternavn) if studentnr is not None: new_person.affect_external_id(co.system_fs, co.externalid_fodselsnr, co.externalid_studentnr) new_person.populate_external_id(co.system_fs, co.externalid_studentnr, studentnr) else: new_person.affect_external_id(co.system_fs, co.externalid_fodselsnr) new_person.populate_external_id(co.system_fs, co.externalid_fodselsnr, fnr) ad_post, ad_post_private, ad_street = _calc_address(person_info) for address_info, ad_const in ((ad_post, co.address_post), (ad_post_private, co.address_post_private), (ad_street, co.address_street)): # TBD: Skal vi slette evt. eksisterende adresse v/None? if address_info is not None: logger.debug("Populating address...") new_person.populate_address(co.system_fs, ad_const, **address_info) # if this is a new Person, there is no entity_id assigned to it # until written to the database. try: op = new_person.write_db() except Exception, e: logger.exception("write_db failed for person %s: %s", fnr, e) # Roll back in case of db exceptions: db.rollback() return
def process_person_callback(person_info): """Called when we have fetched all data on a person from the xml file. Updates/inserts name, address and affiliation information.""" global no_name try: fnr = fodselsnr.personnr_ok( "%06d%05d" % (int(person_info['fodselsdato']), int(person_info['personnr']))) fnr = fodselsnr.personnr_ok(fnr) logger.info("Process %s " % (fnr)) (year, mon, day) = fodselsnr.fodt_dato(fnr) if (year < 1970 and getattr(cereconf, "ENABLE_MKTIME_WORKAROUND", 0) == 1): # Seems to be a bug in time.mktime on some machines year = 1970 except fodselsnr.InvalidFnrError: logger.warn("Ugyldig fødselsnr: %s" % fnr) return gender = co.gender_male if (fodselsnr.er_kvinne(fnr)): gender = co.gender_female etternavn = fornavn = None studentnr = None affiliations = [] address_info = None aktiv_sted = [] aktivemne_sted = [] # Iterate over all person_info entries and extract relevant data if 'aktiv' in person_info: for row in person_info['aktiv']: if studieprog2sko[row['studieprogramkode']] is not None: aktiv_sted.append(int( studieprog2sko[row['studieprogramkode']])) logger.debug("App2akrivts") if 'emnestud' in person_info: for row in person_info['emnestud']: if emne2sko[row['emnekode']] is not None: aktivemne_sted.append(int(emne2sko[row['emnekode']])) logger.debug('Add sko %s based on emne %s', int(emne2sko[row['emnekode']]), row['emnekode']) for dta_type in person_info.keys(): x = person_info[dta_type] p = x[0] if isinstance(p, str): continue # Get name if dta_type in ( 'fagperson', 'opptak', 'tilbud', 'evu', 'privatist_emne', 'privatist_studieprogram', 'alumni', 'emnestud', ): etternavn = p['etternavn'] fornavn = p['fornavn'] if 'studentnr_tildelt' in p: studentnr = p['studentnr_tildelt'] # Get affiliations if dta_type in ('fagperson', ): _process_affiliation( co.affiliation_tilknyttet, co.affiliation_tilknyttet_fagperson, affiliations, _get_sko(p, 'faknr', 'instituttnr', 'gruppenr', 'institusjonsnr')) elif dta_type in ('opptak', ): for row in x: subtype = co.affiliation_status_student_opptak if studieprog2sko[row['studieprogramkode']] in aktiv_sted: subtype = co.affiliation_status_student_aktiv elif row['studierettstatkode'] == 'EVU': subtype = co.affiliation_status_student_evu elif row['studierettstatkode'] == 'FULLFØRT': subtype = co.affiliation_status_student_alumni elif int(row['studienivakode']) >= 900: subtype = co.affiliation_status_student_drgrad elif _is_new_admission(row.get('dato_studierett_tildelt')): subtype = co.affiliation_status_student_ny _process_affiliation(co.affiliation_student, subtype, affiliations, studieprog2sko[row['studieprogramkode']]) elif dta_type in ('emnestud', ): for row in x: subtype = co.affiliation_status_student_emnestud # We may have some situations here where students get # emnestud and aonther affiliation to the same sko, # but this seems to work for now. try: sko = emne2sko[row['emnekode']] except KeyError: logger.warn("Fant ingen emner med koden %s", p['emnekode']) continue if sko in aktiv_sted: subtype = co.affiliation_status_student_aktiv _process_affiliation(co.affiliation_student, subtype, affiliations, sko) elif dta_type in ('privatist_studieprogram', ): _process_affiliation(co.affiliation_student, co.affiliation_status_student_privatist, affiliations, studieprog2sko[p['studieprogramkode']]) elif dta_type in ('privatist_emne', ): try: sko = emne2sko[p['emnekode']] except KeyError: logger.warn("Fant ingen emner med koden %s" % p['emnekode']) continue _process_affiliation(co.affiliation_student, co.affiliation_status_student_privatist, affiliations, sko) elif dta_type in ('perm', ): _process_affiliation(co.affiliation_student, co.affiliation_status_student_aktiv, affiliations, studieprog2sko[p['studieprogramkode']]) elif dta_type in ('tilbud', ): for row in x: _process_affiliation(co.affiliation_student, co.affiliation_status_student_tilbud, affiliations, studieprog2sko[row['studieprogramkode']]) elif dta_type in ('evu', ): _process_affiliation( co.affiliation_student, co.affiliation_status_student_evu, affiliations, _get_sko(p, 'faknr_adm_ansvar', 'instituttnr_adm_ansvar', 'gruppenr_adm_ansvar')) else: logger.debug2("No such affiliation type: %s, skipping", dta_type) if etternavn is None: logger.debug("Ikke noe navn på %s" % fnr) no_name += 1 return # TODO: If the person already exist and has conflicting data from # another source-system, some mechanism is needed to determine the # superior setting. new_person = Factory.get('Person')(db) if fnr in fnr2person_id: new_person.find(fnr2person_id[fnr]) new_person.populate(mx.DateTime.Date(year, mon, day), gender) new_person.affect_names(co.system_fs, co.name_first, co.name_last) new_person.populate_name(co.name_first, fornavn) new_person.populate_name(co.name_last, etternavn) if studentnr is not None: new_person.affect_external_id(co.system_fs, co.externalid_fodselsnr, co.externalid_studentnr) new_person.populate_external_id(co.system_fs, co.externalid_studentnr, studentnr) else: new_person.affect_external_id(co.system_fs, co.externalid_fodselsnr) new_person.populate_external_id(co.system_fs, co.externalid_fodselsnr, fnr) ad_post, ad_post_private, ad_street = _calc_address(person_info) for address_info, ad_const in ((ad_post, co.address_post), (ad_post_private, co.address_post_private), (ad_street, co.address_street)): # TBD: Skal vi slette evt. eksisterende adresse v/None? if address_info is not None: logger.debug("Populating address %s for %s", ad_const, studentnr) new_person.populate_address(co.system_fs, ad_const, **address_info) # if this is a new Person, there is no entity_id assigned to it # until written to the database. try: op = new_person.write_db() except Exception, e: logger.exception("write_db failed for person %s: %s", fnr, e) # Roll back in case of db exceptions: db.rollback() return
def new_project(self, input): """Create a given project. TODO: A lot of this code should be moved into e.g. TSD's OU mixin, or somewhere else to be usable both by various scripts and bofhd. @type input: dict @param input: The survey answers about the requested project. """ pname = input['p_id'] logger.info('New project: %s', pname) ou = self._create_ou(input) # Update the requestee for the project: pe = self._get_person() self._update_person(pe, input) # Give the respondent an affiliation to the project. # If the respondent sets himself as the Project Owner (responsible), it # gets status as the owner. Otherwise we give him PA status: # TBD: do we need to differentiate between owner and PA? status = co.affiliation_status_project_admin if self.fnr == input['rek_owner']: status = co.affiliation_status_project_owner pe.populate_affiliation(source_system=co.system_nettskjema, ou_id=ou.entity_id, status=status, affiliation=co.affiliation_project) pe.write_db() # Check the responsible and give access to the project by an # affiliation: if self.fnr != input['rek_owner']: # TODO: Should we create a person with this ID or not? try: pe2 = self._get_person(input['rek_owner'], create_nonexisting=False) pe2.populate_affiliation( source_system=co.system_nettskjema, ou_id=ou.entity_id, affiliation=co.affiliation_project, status=co.affiliation_status_project_owner) # Note that no name or anything else is now set, so we wait # with the account. pe2.write_db() except Errors.NotFoundError: logger.warn("Project owner not found: %s", input['rek_owner']) # Give the PA an account: ac = self._create_account(pe, ou, input['pa_username']) # Fill the pre approve list with external ids: pre_approve_list = set() for fnr in set(input['p_persons'].split()): for fnr1 in set(fnr.split(',')): try: fnr1 = fodselsnr.personnr_ok(fnr1) except fodselsnr.InvalidFnrError: logger.debug("Ignoring invalid fnr: %s", fnr1) continue except ValueError: logger.debug("Ignoring invalid fnr: %s", fnr1) continue pre_approve_list.add(fnr1) if pre_approve_list: logger.debug("Pre approvals: %s", ', '.join(pre_approve_list)) ou.add_pre_approved_persons(pre_approve_list) ou.write_db() # TODO:How should we signal that a new project is waiting for approval? return True
def approve_persons(self, input): """Let project owner and PAs approve more persons to their project. Sending this through Nettskjema is needed as we have no web GUI inside TSD in the beginning, and project admins need a way to approve people for their projects. """ # Find the project: pid = input['p_id'] ou.clear() ou.find_by_tsd_projectid(pid) logger.info('Approve persons for project: %s', pid) # Find the requestor pe = self._get_person() # The requestor must be owner or PA: if not list( pe.list_affiliations( person_id=pe.entity_id, affiliation=co.affiliation_project, status=(co.affiliation_status_project_owner, co.affiliation_status_project_admin), ou_id=ou.entity_id)): # TODO: Delete the JSON file? raise BadInputError("Person %s is not PA of project %s" % (pe.entity_id, pid)) # Update contact info for PA: self._update_person(pe, input) # Try to find and add the given person to the project pe2 = Factory.get('Person')(db) pre_approvals = set() for fnr in set(input['p_persons'].split()): for fnr1 in set(fnr.split(',')): try: fnr1 = fodselsnr.personnr_ok(fnr1) except fodselsnr.InvalidFnrError: logger.debug("Ignoring invalid fnr: %s", fnr1) continue # Find the person: try: pe2 = self._get_person(fnr1, create_nonexisting=False) except Errors.NotFoundError: logger.info( "Person %s not found, added to pre approve list", fnr1) pre_approvals.add(fnr1) continue # Affiliate person to project: if not pe2.list_affiliations( person_id=pe2.entity_id, ou_id=ou.entity_id, affiliation=co.affiliation_project): logger.info("Approve person %s for project: %s", fnr1, pid) pe2.populate_affiliation( source_system=co.system_nettskjema, ou_id=ou.entity_id, affiliation=co.affiliation_project, status=co.affiliation_status_project_member) # Remove pending aff, if set. Note that this also removes any # other pending statuses, # if we should create more of those in the future. pe2.delete_affiliation(ou_id=ou.entity_id, affiliation=co.affiliation_pending, source=co.system_nettskjema) pe2.write_db() # Find the member's project account, if it exist: accounts = self._get_project_account(pe2, ou) if not accounts: logger.info( "No project account for %s, added to approve list", fnr1) pre_approvals.add(fnr1) continue ac.clear() ac.find(accounts[0]['account_id']) logger.info("Approving person %s PM to %s: %s ", fnr1, ac.account_name, pid) # Update the account types: ac.set_account_type(ou.entity_id, co.affiliation_project) # TODO: check if this works if the type is already set: ac.del_account_type(ou.entity_id, co.affiliation_pending) ac.del_account_type(ou.entity_id, co.affiliation_pending) # Remove quarantine: ac.delete_entity_quarantine(co.quarantine_not_approved) ac.write_db() # Promote posix: if ou.is_approved(): pu = Factory.get('PosixUser')(db) try: pu.find(ac.entity_id) except Errors.NotFoundError: uid = pu.get_free_uid() pu.populate(uid, None, None, co.posix_shell_bash, parent=ac, creator_id=systemaccount_id) logger.debug("Promote POSIX, UID: %s", ac.entity_id, uid) pu.write_db() # Store those not processed for later approval: if pre_approvals: logger.debug("Remaining non-existing persons: %d", len(pre_approvals)) ou.add_pre_approved_persons(pre_approvals) ou.write_db() return True
def process_person_callback(person_info): """Called when we have fetched all data on a person from the xml file. Updates/inserts name, address and affiliation information.""" global no_name try: fnr = fodselsnr.personnr_ok( "%06d%05d" % (int(person_info['fodselsdato']), int(person_info['personnr']))) fnr = fodselsnr.personnr_ok(fnr) logger.info("Process %s " % (fnr)) (year, mon, day) = fodselsnr.fodt_dato(fnr) if (year < 1970 and getattr(cereconf, "ENABLE_MKTIME_WORKAROUND", 0) == 1): # Seems to be a bug in time.mktime on some machines year = 1970 except fodselsnr.InvalidFnrError: logger.warn("Ugyldig fødselsnr: %s" % fnr) return gender = co.gender_male if(fodselsnr.er_kvinne(fnr)): gender = co.gender_female etternavn = fornavn = None studentnr = None affiliations = [] address_info = None aktiv_sted = [] aktivemne_sted = [] # Iterate over all person_info entries and extract relevant data if 'aktiv' in person_info: for row in person_info['aktiv']: if studieprog2sko[row['studieprogramkode']] is not None: aktiv_sted.append( int(studieprog2sko[row['studieprogramkode']])) logger.debug("App2akrivts") if 'emnestud' in person_info: for row in person_info['emnestud']: if emne2sko[row['emnekode']] is not None: aktivemne_sted.append(int(emne2sko[row['emnekode']])) logger.debug('Add sko %s based on emne %s', int(emne2sko[row['emnekode']]), row['emnekode']) for dta_type in person_info.keys(): x = person_info[dta_type] p = x[0] if isinstance(p, str): continue # Get name if dta_type in ( 'fagperson', 'opptak', 'tilbud', 'evu', 'privatist_emne', 'privatist_studieprogram', 'alumni', 'emnestud', ): etternavn = p['etternavn'] fornavn = p['fornavn'] if 'studentnr_tildelt' in p: studentnr = p['studentnr_tildelt'] # Get affiliations if dta_type in ('fagperson',): _process_affiliation(co.affiliation_tilknyttet, co.affiliation_tilknyttet_fagperson, affiliations, _get_sko(p, 'faknr', 'instituttnr', 'gruppenr', 'institusjonsnr')) elif dta_type in ('opptak', ): for row in x: subtype = co.affiliation_status_student_opptak if studieprog2sko[row['studieprogramkode']] in aktiv_sted: subtype = co.affiliation_status_student_aktiv elif row['studierettstatkode'] == 'EVU': subtype = co.affiliation_status_student_evu elif row['studierettstatkode'] == 'FULLFØRT': subtype = co.affiliation_status_student_alumni elif int(row['studienivakode']) >= 900: subtype = co.affiliation_status_student_drgrad elif _is_new_admission(row.get('dato_studierett_tildelt')): subtype = co.affiliation_status_student_ny _process_affiliation(co.affiliation_student, subtype, affiliations, studieprog2sko[row['studieprogramkode']]) elif dta_type in ('emnestud',): for row in x: subtype = co.affiliation_status_student_emnestud # We may have some situations here where students get # emnestud and aonther affiliation to the same sko, # but this seems to work for now. try: sko = emne2sko[row['emnekode']] except KeyError: logger.warn("Fant ingen emner med koden %s", p['emnekode']) continue if sko in aktiv_sted: subtype = co.affiliation_status_student_aktiv _process_affiliation(co.affiliation_student, subtype, affiliations, sko) elif dta_type in ('privatist_studieprogram',): _process_affiliation(co.affiliation_student, co.affiliation_status_student_privatist, affiliations, studieprog2sko[p['studieprogramkode']]) elif dta_type in ('privatist_emne',): try: sko = emne2sko[p['emnekode']] except KeyError: logger.warn("Fant ingen emner med koden %s" % p['emnekode']) continue _process_affiliation(co.affiliation_student, co.affiliation_status_student_privatist, affiliations, sko) elif dta_type in ('perm',): _process_affiliation(co.affiliation_student, co.affiliation_status_student_aktiv, affiliations, studieprog2sko[p['studieprogramkode']]) elif dta_type in ('tilbud',): for row in x: _process_affiliation(co.affiliation_student, co.affiliation_status_student_tilbud, affiliations, studieprog2sko[row['studieprogramkode']]) elif dta_type in ('evu', ): _process_affiliation(co.affiliation_student, co.affiliation_status_student_evu, affiliations, _get_sko(p, 'faknr_adm_ansvar', 'instituttnr_adm_ansvar', 'gruppenr_adm_ansvar')) else: logger.debug2("No such affiliation type: %s, skipping", dta_type) if etternavn is None: logger.debug("Ikke noe navn på %s" % fnr) no_name += 1 return # TODO: If the person already exist and has conflicting data from # another source-system, some mechanism is needed to determine the # superior setting. new_person = Factory.get('Person')(db) if fnr in fnr2person_id: new_person.find(fnr2person_id[fnr]) new_person.populate(mx.DateTime.Date(year, mon, day), gender) new_person.affect_names(co.system_fs, co.name_first, co.name_last) new_person.populate_name(co.name_first, fornavn) new_person.populate_name(co.name_last, etternavn) if studentnr is not None: new_person.affect_external_id(co.system_fs, co.externalid_fodselsnr, co.externalid_studentnr) new_person.populate_external_id(co.system_fs, co.externalid_studentnr, studentnr) else: new_person.affect_external_id(co.system_fs, co.externalid_fodselsnr) new_person.populate_external_id(co.system_fs, co.externalid_fodselsnr, fnr) ad_post, ad_post_private, ad_street = _calc_address(person_info) for address_info, ad_const in ((ad_post, co.address_post), (ad_post_private, co.address_post_private), (ad_street, co.address_street)): # TBD: Skal vi slette evt. eksisterende adresse v/None? if address_info is not None: logger.debug("Populating address %s for %s", ad_const, studentnr) new_person.populate_address(co.system_fs, ad_const, **address_info) # if this is a new Person, there is no entity_id assigned to it # until written to the database. try: op = new_person.write_db() except Exception, e: logger.exception("write_db failed for person %s: %s", fnr, e) # Roll back in case of db exceptions: db.rollback() return