def test_grad_status(self): self.assertEqual( set(dict(STATUS_CHOICES).keys()) | set([None]), set(SHORT_STATUSES.keys())) client = Client() client.login_user('dzhao') this_sem = Semester.current() # clear the deck on this student's statuses gs = self.__make_test_grad() gs.gradstatus_set.all().delete() s1 = GradStatus(student=gs, status='COMP', start=this_sem.offset(-4)) s1.save() s2 = GradStatus(student=gs, status='ACTI', start=this_sem.offset(-3)) s2.save() s3 = GradStatus(student=gs, status='LEAV', start=this_sem.offset(2)) s3.save() gs = GradStudent.objects.get( id=gs.id) # make sure we get what's in the database now self.assertEqual(gs.current_status, 'ACTI') # check status in a particular semester results self.assertEqual(gs.status_as_of(this_sem.offset(-8)), None) self.assertEqual(gs.status_as_of(this_sem.offset(-4)), 'COMP') self.assertEqual(gs.status_as_of(this_sem.offset(-3)), 'ACTI') self.assertEqual(gs.status_as_of(this_sem), 'ACTI') self.assertEqual(gs.status_as_of(this_sem.offset(1)), 'ACTI') self.assertEqual(gs.status_as_of(this_sem.offset(2)), 'LEAV') self.assertEqual(gs.status_as_of(this_sem.offset(3)), 'LEAV') # grad.tasks.update_statuses_to_current will put this student on LEAV on the first day of that future semester # The following is no longer true. The latest status (based on start date) is the one that wins, period, # whether it's manually put in or imported from SIMS. # # check that "active" statuses are preferred over "applicant" statuses in status calcs # s4 = GradStatus(student=gs, status='COMP', start=this_sem.offset(-3)) # s4.save() # self.assertEqual(gs.status_as_of(this_sem.offset(-3)), 'ACTI') # because of insanity that makes strange sense, application-decision statuses propagate back a semester gs.gradstatus_set.all().delete() s1 = GradStatus(student=gs, status='COMP', start=this_sem) s1.save() s2 = GradStatus(student=gs, status='REJE', start=this_sem.offset(1)) s2.save() self.assertEqual(gs.status_as_of(this_sem.offset(-3)), 'COMP') self.assertEqual(gs.status_as_of(this_sem), 'REJE') self.assertEqual(gs.status_as_of(this_sem.offset(1)), 'REJE')
def create_fake_gradstatuses(): # currently, the end semester can be after the start semester # TODO: fix that. maybe. statuses = tuple(x for x,_ in models.STATUS_CHOICES) semesters = list(Semester.objects.all()) for _ in range(200): gs = GradStatus( student=random.choice(all_gradstudents.values()), status=random.choice(statuses), start=random.choice(semesters), end=random.choice(semesters) if randbool() else None, ) gs.save() all_gradstatuses.append(gs)
def update_local_data(self, student_info, verbosity, dry_run): if self.grad_program.unit.slug == 'cmpt': return # make sure the student is "active" as of the start of this semester, since they're taking courses statuses = student_info['statuses'] semester = self.semester effdt = self.effdt key = self.import_key() # Option 1: we're already active effective_statuses = [s for s in statuses if s.start.name <= self.strm and (not s.start_date or s.start_date <= effdt)] if effective_statuses and effective_statuses[-1].status == 'ACTI': s = effective_statuses[-1] if SIMS_SOURCE not in s.config: s.config[SIMS_SOURCE] = key if not dry_run: s.save() return # Option 2: there's an active status this semester, but it's not the most recent active_semester_statuses = [s for s in statuses if s.start.name == self.strm and s.status == 'ACTI'] if active_semester_statuses: st = active_semester_statuses[-1] if SIMS_SOURCE in st.config and st.config[SIMS_SOURCE] == key: # This happens for a couple of students with a 'confirmed acceptance' on the same effdt as the semester start # Kick it to make it effective for this semester, but don't bother reporting it. effdt = effdt + datetime.timedelta(days=1) elif verbosity > 1: print "* Adjusting date of grad status: %s/%s is '%s' as of %s (was taking courses)." % (self.emplid, self.unit.slug, SHORT_STATUSES['ACTI'], self.strm) st.start_date = effdt st.config[SIMS_SOURCE] = key if not dry_run: st.save() else: # Option 3: need to add an active status if verbosity: print "Adding grad status: %s/%s is '%s' as of %s (was taking courses)." % (self.emplid, self.unit.slug, SHORT_STATUSES['ACTI'], self.strm) st = GradStatus(student=student_info['student'], status='ACTI', start=semester, start_date=effdt) st.config[SIMS_SOURCE] = key if not dry_run: st.save() student_info['statuses'].append(st) # re-sort if we added something, so we find things right on the next check student_info['statuses'].sort(key=lambda ph: (ph.start.name, ph.start_date or datetime.date(1900,1,1)))
def update_local_data(self, student_info, verbosity, dry_run): # if self.grad_program.unit.slug == 'cmpt': # return # make sure the student is "active" as of the start of this semester, since they're taking courses statuses = student_info['statuses'] semester = self.semester effdt = self.effdt key = self.import_key() # Option 1: we're already active effective_statuses = [s for s in statuses if s.start.name <= self.strm and (not s.start_date or s.start_date <= effdt)] if effective_statuses and effective_statuses[-1].status == 'ACTI': s = effective_statuses[-1] if SIMS_SOURCE not in s.config: s.config[SIMS_SOURCE] = key if not dry_run: s.save() return # Option 2: there's an active status this semester, but it's not the most recent active_semester_statuses = [s for s in statuses if s.start.name == self.strm and s.status == 'ACTI'] if active_semester_statuses: st = active_semester_statuses[-1] if SIMS_SOURCE in st.config and st.config[SIMS_SOURCE] == key: # This happens for a couple of students with a 'confirmed acceptance' on the same effdt as the semester start # Kick it to make it effective for this semester, but don't bother reporting it. effdt = effdt + datetime.timedelta(days=1) elif verbosity > 1: print("* Adjusting date of grad status: %s/%s is '%s' as of %s (was taking courses)." % (self.emplid, self.unit.slug, SHORT_STATUSES['ACTI'], self.strm)) st.start_date = effdt st.config[SIMS_SOURCE] = key if not dry_run: st.save() else: # Option 3: need to add an active status if verbosity: print("Adding grad status: %s/%s is '%s' as of %s (was taking courses)." % (self.emplid, self.unit.slug, SHORT_STATUSES['ACTI'], self.strm)) st = GradStatus(student=student_info['student'], status='ACTI', start=semester, start_date=effdt) st.config[SIMS_SOURCE] = key if not dry_run: st.save() student_info['statuses'].append(st) # re-sort if we added something, so we find things right on the next check student_info['statuses'].sort(key=lambda ph: (ph.start.name, ph.start_date or datetime.date(1900,1,1)))
def test_advanced_search_3(self): client = Client() client.login_user('dzhao') this_sem = Semester.current() units = [ r.unit for r in Role.objects_fresh.filter(person__userid='dzhao', role='GRAD') ] gs = self.__make_test_grad() gs.gradstatus_set.all().delete() s1 = GradStatus(student=gs, status='COMP', start=this_sem.offset(-4)) s1.save() s2 = GradStatus(student=gs, status='ACTI', start=this_sem.offset(-3)) s2.save() s3 = GradStatus(student=gs, status='LEAV', start=this_sem.offset(2)) s3.save() gs.update_status_fields() # test current-status searching form = SearchForm( QueryDict('student_status=ACTI&columns=person.emplid')) active_now = form.search_results(units) self.assertIn(gs, active_now) form = SearchForm( QueryDict('student_status=LEAV&columns=person.emplid')) leave_now = form.search_results(units) self.assertNotIn(gs, leave_now)
def test_advanced_search_3(self): client = Client() test_auth(client, 'ggbaker') this_sem = Semester.current() units = [ r.unit for r in Role.objects.filter(person__userid='ggbaker', role='GRAD') ] gs = self.__make_test_grad() gs.gradstatus_set.all().delete() s1 = GradStatus(student=gs, status='COMP', start=this_sem.offset(-4)) s1.save() s2 = GradStatus(student=gs, status='ACTI', start=this_sem.offset(-3)) s2.save() s3 = GradStatus(student=gs, status='LEAV', start=this_sem.offset(2)) s3.save() # test current-status searching form = SearchForm( QueryDict('student_status=ACTI&columns=person.emplid')) active_now = form.search_results(units) self.assertIn(gs, active_now) form = SearchForm( QueryDict('student_status=LEAV&columns=person.emplid')) leave_now = form.search_results(units) self.assertNotIn(gs, leave_now) # test status-as-of searching form = SearchForm( QueryDict( 'student_status=ACTI&status_asof=%s&columns=person.emplid' % (this_sem.offset(-4).name))) active_past = form.search_results(units) self.assertNotIn(gs, active_past) form = SearchForm( QueryDict( 'student_status=COMP&status_asof=%s&columns=person.emplid' % (this_sem.offset(-4).name))) applic_past = form.search_results(units) #self.assertIn(gs, applic_past) form = SearchForm( QueryDict( 'student_status=ACTI&status_asof=%s&columns=person.emplid' % (this_sem.offset(3).name))) active_later = form.search_results(units) self.assertNotIn(gs, active_later) form = SearchForm( QueryDict( 'student_status=LEAV&status_asof=%s&columns=person.emplid' % (this_sem.offset(3).name))) leave_later = form.search_results(units) self.assertIn(gs, leave_later)
def test_advanced_search_3(self): client = Client() test_auth(client, 'ggbaker') this_sem = Semester.current() units = [r.unit for r in Role.objects.filter(person__userid='ggbaker', role='GRAD')] gs = self.__make_test_grad() gs.gradstatus_set.all().delete() s1 = GradStatus(student=gs, status='COMP', start=this_sem.offset(-4)) s1.save() s2 = GradStatus(student=gs, status='ACTI', start=this_sem.offset(-3)) s2.save() s3 = GradStatus(student=gs, status='LEAV', start=this_sem.offset(2)) s3.save() # test current-status searching form = SearchForm(QueryDict('student_status=ACTI&columns=person.emplid')) active_now = form.search_results(units) self.assertIn(gs, active_now) form = SearchForm(QueryDict('student_status=LEAV&columns=person.emplid')) leave_now = form.search_results(units) self.assertNotIn(gs, leave_now) # test status-as-of searching form = SearchForm(QueryDict('student_status=ACTI&status_asof=%s&columns=person.emplid' % (this_sem.offset(-4).name))) active_past = form.search_results(units) self.assertNotIn(gs, active_past) form = SearchForm(QueryDict('student_status=COMP&status_asof=%s&columns=person.emplid' % (this_sem.offset(-4).name))) applic_past = form.search_results(units) #self.assertIn(gs, applic_past) form = SearchForm(QueryDict('student_status=ACTI&status_asof=%s&columns=person.emplid' % (this_sem.offset(3).name))) active_later = form.search_results(units) self.assertNotIn(gs, active_later) form = SearchForm(QueryDict('student_status=LEAV&status_asof=%s&columns=person.emplid' % (this_sem.offset(3).name))) leave_later = form.search_results(units) self.assertIn(gs, leave_later)
def process_pcs_row(row, column, rownum, unit, semester, user): """ Process a single row from the PCS import """ appsemester = semester.previous_semester() warnings = [] ident = "in row %i" % (rownum) appid = row[column['appid']] emplid = row[column['emplid']] program = row[column['program']] # get Person, from SIMS if necessary try: p = Person.objects.get(emplid=int(emplid)) except ValueError: warnings.append("Bad emplid %s: not processing that row." % (ident)) return warnings except Person.DoesNotExist: try: p = add_person(emplid) except SIMSProblem as e: return e.message ident = 'for "%s"' % (p.name()) # update information on the Person email = row[column['email']] if email: p.config['applic_email'] = email dob = row[column['dob']] if dob: try: dt = datetime.datetime.strptime(dob, "%Y-%m-%d") p.config['birthdate'] = dt.date().isoformat() except ValueError: warnings.append("Bad birthdate %s." % (ident)) # get extended SIMS data data = grad_student_info(emplid) p.config.update(data) p.save() #print "Importing %s" % (p) # get GradStudent, creating if necessary # a unique identifier for this application, so we can detect repeated imports (and handle gracefully) uid = "%s-%s-%s-%s" % (unit.slug, semester.name, appid, emplid) # TODO: wrong, wrong, wrong. Figure out how to select program from import data program = GradProgram.objects.filter(unit=unit)[0] # find the old GradStudent if possible gss = GradStudent.objects.filter(program__unit=unit, person=p) gs = None for g in gss: if 'app_id' in g.config and g.config['app_id'] == uid: gs = g break if not gs: gs = GradStudent(program=program, person=p) gs.config['app_id'] = uid resarea = row[column['resarea']] firstlang = row[column['firstlang']] gs.research_area = resarea gs.mother_tongue = firstlang gs.created_by = user.userid gs.updated_by = user.userid gs.config['start_semester'] = semester.name gs.save() complete = row[column['complete']].strip() decision = row[column['decision']].strip() notes = row[column['notes']].strip() gs.config['decisionnotes'] = notes old_st = GradStatus.objects.filter(student=gs, start__name__gte=semester.name) if not old_st: # if no old status for current semester, create one # application completion status if complete == 'AppConfirm': st = GradStatus(student=gs, status="COMP", start=appsemester, end=None, notes="PCS import") st.save() elif complete == '': st = GradStatus(student=gs, status="INCO", start=appsemester, end=None, notes="PCS import") st.save() else: warnings.append( 'Unknown "Confirmation of Completion of Application" value %s.' % (ident)) # decision status if decision == 'DECL': st = GradStatus(student=gs, status="DECL", start=appsemester, end=None, notes="PCS import") st.save() elif decision == '': st = GradStatus(student=gs, status="OFFO", start=appsemester, end=None, notes="PCS import") st.save() elif decision == 'R': st = GradStatus(student=gs, status="REJE", start=appsemester, end=None, notes="PCS import") st.save() elif decision == 'HOLD': st = GradStatus(student=gs, status="HOLD", start=appsemester, end=None, notes="PCS import") st.save() elif decision == 'AMScT': # TODO: bump program to MSc thesis st = GradStatus(student=gs, status="CONF", start=appsemester, end=None, notes="PCS import") st.save() elif decision == 'AMScC': # TODO: bump program to MSc course-based st = GradStatus(student=gs, status="CONF", start=appsemester, end=None, notes="PCS import") st.save() # potential supervisor potsuper = row[column['potsuper']] if potsuper: superv = None external = None try: ps_last, ps_first = potsuper.split(', ') except ValueError: warnings.append( 'Bad potential supervisor name %s: will store them as an "external" supervisor.' % (ident)) external = potsuper else: potentials = possible_supervisor_people([unit]) potential_ids = [p.id for p in potentials] query = Q(last_name=ps_last, first_name=ps_first) | Q( last_name=ps_last, pref_first_name=ps_first) people = Person.objects.filter(query, id__in=potential_ids) if people.count() == 1: superv = people[0] else: warnings.append( 'Coundn\'t find potential supervisor %s: will store them as an "external" supervisor.' % (ident)) external = potsuper old_s = Supervisor.objects.filter(student=gs, supervisor_type='POT') if old_s: s = old_s[0] else: s = Supervisor(student=gs, supervisor_type='POT') s.superv = superv s.external = external s.position = 0 s.created_by = user.userid s.modified_by = user.userid s.save() l = LogEntry(userid=user.userid, description="Imported grad record for %s (%s) from PCS" % (p.name(), p.emplid), related_object=gs) l.save() return warnings
def create_grad(): """ Test data for grad, ta, ra """ from grad.models import GradProgram, GradStudent, GradProgramHistory, GradStatus, ScholarshipType, Scholarship, \ OtherFunding, Promise, GradRequirement, CompletedRequirement, LetterTemplate, GradFlag, GradFlagValue, Supervisor cmpt = Unit.objects.get(slug='cmpt') ensc = Unit.objects.get(slug='ensc') mse = Unit.objects.get(slug='mse') # some admin roles d = Person.objects.get(userid='dzhao') r1 = Role(person=d, role='GRAD', unit=cmpt, expiry=role_expiry) r1.save() r2 = Role(person=Person.objects.get(userid='popowich'), role="GRPD", unit=cmpt, expiry=role_expiry) r2.save() roles = [r1, r2] # departmental data st1 = ScholarshipType(unit=cmpt, name='Scholarship-o-rama', eligible=False) st1.save() st2 = ScholarshipType(unit=cmpt, name='Generic Scholarship #8', eligible=True) st2.save() scholarship_types = [st1, st2] templates = [ { "unit": cmpt, "label": "offer", "content": u"Congratulations, {{first_name}}, we would like to offer you admission to the {{program}} program in Computing Science at SFU.\r\n\r\nThis is gööd news. Really." }, { "unit": cmpt, "label": "visa", "content": "This is to confirm that {{title}} {{first_name}} {{last_name}} is currently enrolled as a full time student in the {{program}} in the School of Computing Science at SFU." }, { "unit": cmpt, "label": "Funding", "content": "This is to confirm that {{title}} {{first_name}} {{last_name}} is a student in the School of Computing Science's {{program}} program. {{He_She}} has been employed as follows:\r\n\r\n{% if tafunding %}Teaching assistant responsibilities include providing tutorials, office hours and marking assignments. {{title}} {{last_name}}'s assignments have been:\r\n\r\n{{ tafunding }}{% endif %}\r\n{% if rafunding %}Research assistants assist/provide research services to faculty. {{title}} {{last_name}}'s assignments have been:\r\n\r\n{{ rafunding }}{% endif %}\r\n{% if scholarships %}{{title}} {{last_name}} has received the following scholarships:\r\n\r\n{{ scholarships }}{% endif %}\r\n\r\n{{title}} {{last_name}} is making satisfactory progress." }, ] for data in templates: t = LetterTemplate(**data) t.save() p = GradProgram(unit=cmpt, label='MSc Course', description='MSc Course option') p.save() p = GradProgram(unit=cmpt, label='MSc Proj', description='MSc Project option') p.save() p = GradProgram(unit=cmpt, label='MSc Thesis', description='MSc Thesis option') p.save() p = GradProgram(unit=cmpt, label='PhD', description='Doctor of Philosophy') p.save() p = GradProgram(unit=cmpt, label='Qualifying', description='Qualifying student') p.save() p = GradProgram(unit=cmpt, label='Special', description='Special Arrangements') p.save() gr = GradRequirement(program=p, description='Achieved Speciality') gr.save() for p in GradProgram.objects.filter(unit=cmpt): gr = GradRequirement(program=p, description='Found campus') gr.save() gf = GradFlag(unit=cmpt, label='Dual Degree Program') gf.save() gf = GradFlag(unit=cmpt, label='Co-op') gf.save() grads = list(Person.objects.filter(last_name='Grad')) programs = list(GradProgram.objects.all()) today = datetime.date.today() starts = Semester.objects.filter(start__gt=today - datetime.timedelta(1000), start__lt=today) supervisors = list( set([ m.person for m in Member.objects.filter( role='INST').select_related('person') ])) # create GradStudents (and associated data) in a vaguely realistic way for g in grads + random.sample(grads, 5): # put a few in two programs p = random.choice(programs) start = random.choice(starts) sstart = start.start if random.randint(1, 2) == 1: end = start.offset(random.randint(3, 9)) else: end = None gs = GradStudent(person=g, program=p, research_area=randname(8) + 'ology', campus=random.choice([x for x, _ in CAMPUS_CHOICES]), is_canadian=randnullbool()) gs.save() gph = GradProgramHistory(student=gs, program=p, start_semester=start) gph.save() if random.randint(1, 3) == 1: p2 = random.choice( [p2 for p2 in programs if p != p2 and p.unit == p2.unit]) gph = GradProgramHistory(student=gs, program=p2, start_semester=start.offset( random.randint(1, 4))) gph.save() s = GradStatus(student=gs, status='COMP', start=start, start_date=sstart - datetime.timedelta(days=100)) s.save() if random.randint(1, 4) == 1: s = GradStatus(student=gs, status='REJE', start=start, start_date=sstart - datetime.timedelta(days=80)) s.save() else: s = GradStatus(student=gs, status='OFFO', start=start, start_date=sstart - datetime.timedelta(days=80)) s.save() s = GradStatus(student=gs, status='ACTI', start=start, start_date=sstart) s.save() if end: if random.randint(1, 3): s = GradStatus(student=gs, status='WIDR', start=end, start_date=end.start) s.save() else: s = GradStatus(student=gs, status='GRAD', start=end, start_date=end.start) s.save() gs.update_status_fields() # give some money sch = Scholarship(student=gs, scholarship_type=random.choice(scholarship_types)) sch.amount = 2000 sch.start_semester = start sch.end_semester = start.offset(2) sch.save() of = OtherFunding(student=gs, semester=start.offset(3)) of.amount = 1300 of.description = "Money fell from the sky" of.save() # promise p = Promise(student=gs, start_semester=start, end_semester=start.offset(2), amount=10000) p.save() p = Promise(student=gs, start_semester=start.offset(3), end_semester=start.offset(5), amount=10000) p.save() # flags if random.randint(1, 3) == 1: cr = CompletedRequirement(requirement=gr, student=gs, semester=start.offset(1)) cr.save() if random.randint(1, 4) == 1: gfv = GradFlagValue(flag=gf, student=gs, value=True) gfv.save() # supervisors if random.randint(1, 3) != 1: p = random.choice(supervisors) s = Supervisor(student=gs, supervisor=p, supervisor_type='POT') s.save() if random.randint(1, 2) == 1: s = Supervisor(student=gs, supervisor=p, supervisor_type='SEN') s.save() s = Supervisor(student=gs, supervisor=random.choice(supervisors), supervisor_type='COM') s.save() return itertools.chain( roles, programs, scholarship_types, GradRequirement.objects.all(), LetterTemplate.objects.all(), GradFlag.objects.all(), GradStudent.objects.all(), GradProgramHistory.objects.all(), GradStatus.objects.all(), Scholarship.objects.all(), OtherFunding.objects.all(), Promise.objects.all(), CompletedRequirement.objects.all(), GradFlagValue.objects.all(), Supervisor.objects.all(), )
def test_grad_status(self): client = Client() test_auth(client, 'ggbaker') this_sem = Semester.current() # clear the deck on this student's statuses gs = self.__make_test_grad() gs.gradstatus_set.all().delete() s1 = GradStatus(student=gs, status='COMP', start=this_sem.offset(-4)) s1.save() s2 = GradStatus(student=gs, status='ACTI', start=this_sem.offset(-3)) s2.save() s3 = GradStatus(student=gs, status='LEAV', start=this_sem.offset(2)) s3.save() gs = GradStudent.objects.get(id=gs.id) # make sure we get what's in the database now self.assertEqual(gs.current_status, 'ACTI') # check status in a particular semester results self.assertEqual(gs.status_as_of(this_sem.offset(-6)), None) self.assertEqual(gs.status_as_of(this_sem.offset(-4)), 'COMP') self.assertEqual(gs.status_as_of(this_sem.offset(-3)), 'ACTI') self.assertEqual(gs.status_as_of(this_sem), 'ACTI') self.assertEqual(gs.status_as_of(this_sem.offset(1)), 'ACTI') self.assertEqual(gs.status_as_of(this_sem.offset(2)), 'LEAV') self.assertEqual(gs.status_as_of(this_sem.offset(3)), 'LEAV') # grad.tasks.update_statuses_to_current will put this student on LEAV on the first day of that future semester # check that "active" statuses are preferred over "applicant" statuses in status calcs s4 = GradStatus(student=gs, status='COMP', start=this_sem.offset(-3)) s4.save() self.assertEqual(gs.status_as_of(this_sem.offset(-3)), 'ACTI') # because of insanity that makes strange sense, application-decision statuses propagate back a semester gs.gradstatus_set.all().delete() s1 = GradStatus(student=gs, status='COMP', start=this_sem) s1.save() s2 = GradStatus(student=gs, status='REJE', start=this_sem.offset(1)) s2.save() self.assertEqual(gs.status_as_of(this_sem.offset(-1)), 'COMP') self.assertEqual(gs.status_as_of(this_sem), 'REJE') self.assertEqual(gs.status_as_of(this_sem.offset(1)), 'REJE')
def test_grad_status(self): client = Client() test_auth(client, 'ggbaker') this_sem = Semester.current() # clear the deck on this student's statuses gs = self.__make_test_grad() gs.gradstatus_set.all().delete() s1 = GradStatus(student=gs, status='COMP', start=this_sem.offset(-4)) s1.save() s2 = GradStatus(student=gs, status='ACTI', start=this_sem.offset(-3)) s2.save() s3 = GradStatus(student=gs, status='LEAV', start=this_sem.offset(2)) s3.save() gs = GradStudent.objects.get( id=gs.id) # make sure we get what's in the database now self.assertEqual(gs.current_status, 'ACTI') # check status in a particular semester results self.assertEqual(gs.status_as_of(this_sem.offset(-6)), None) self.assertEqual(gs.status_as_of(this_sem.offset(-4)), 'COMP') self.assertEqual(gs.status_as_of(this_sem.offset(-3)), 'ACTI') self.assertEqual(gs.status_as_of(this_sem), 'ACTI') self.assertEqual(gs.status_as_of(this_sem.offset(1)), 'ACTI') self.assertEqual(gs.status_as_of(this_sem.offset(2)), 'LEAV') self.assertEqual(gs.status_as_of(this_sem.offset(3)), 'LEAV') # grad.tasks.update_statuses_to_current will put this student on LEAV on the first day of that future semester # check that "active" statuses are preferred over "applicant" statuses in status calcs s4 = GradStatus(student=gs, status='COMP', start=this_sem.offset(-3)) s4.save() self.assertEqual(gs.status_as_of(this_sem.offset(-3)), 'ACTI') # because of insanity that makes strange sense, application-decision statuses propagate back a semester gs.gradstatus_set.all().delete() s1 = GradStatus(student=gs, status='COMP', start=this_sem) s1.save() s2 = GradStatus(student=gs, status='REJE', start=this_sem.offset(1)) s2.save() self.assertEqual(gs.status_as_of(this_sem.offset(-1)), 'COMP') self.assertEqual(gs.status_as_of(this_sem), 'REJE') self.assertEqual(gs.status_as_of(this_sem.offset(1)), 'REJE')
def create_grad(): """ Test data for grad, ta, ra """ from grad.models import GradProgram, GradStudent, GradProgramHistory, GradStatus, ScholarshipType, Scholarship, \ OtherFunding, Promise, GradRequirement, CompletedRequirement, LetterTemplate, GradFlag, GradFlagValue, Supervisor cmpt = Unit.objects.get(slug='cmpt') ensc = Unit.objects.get(slug='ensc') mse = Unit.objects.get(slug='mse') # some admin roles d = Person.objects.get(userid='dzhao') r1 = Role(person=d, role='GRAD', unit=cmpt) r1.save() r2 = Role(person=Person.objects.get(userid='popowich'), role="GRPD", unit=cmpt) r2.save() roles = [r1, r2] # departmental data st1 = ScholarshipType(unit=cmpt, name='Scholarship-o-rama', eligible=False) st1.save() st2 = ScholarshipType(unit=cmpt, name='Generic Scholarship #8', eligible=True) st2.save() scholarship_types = [st1, st2] templates = [ {"unit": cmpt, "label": "offer", "content": "Congratulations, {{first_name}}, we would like to offer you admission to the {{program}} program in Computing Science at SFU.\r\n\r\nThis is good news. Really." }, {"unit": cmpt, "label": "visa", "content": "This is to confirm that {{title}} {{first_name}} {{last_name}} is currently enrolled as a full time student in the {{program}} in the School of Computing Science at SFU." }, {"unit": cmpt, "label": "Funding", "content": "This is to confirm that {{title}} {{first_name}} {{last_name}} is a student in the School of Computing Science's {{program}} program. {{He_She}} has been employed as follows:\r\n\r\n{% if tafunding %}Teaching assistant responsibilities include providing tutorials, office hours and marking assignments. {{title}} {{last_name}}'s assignments have been:\r\n\r\n{{ tafunding }}{% endif %}\r\n{% if rafunding %}Research assistants assist/provide research services to faculty. {{title}} {{last_name}}'s assignments have been:\r\n\r\n{{ rafunding }}{% endif %}\r\n{% if scholarships %}{{title}} {{last_name}} has received the following scholarships:\r\n\r\n{{ scholarships }}{% endif %}\r\n\r\n{{title}} {{last_name}} is making satisfactory progress." }, ] for data in templates: t = LetterTemplate(**data) t.save() p = GradProgram(unit=cmpt, label='MSc Course', description='MSc Course option') p.save() p = GradProgram(unit=cmpt, label='MSc Proj', description='MSc Project option') p.save() p = GradProgram(unit=cmpt, label='MSc Thesis', description='MSc Thesis option') p.save() p = GradProgram(unit=cmpt, label='PhD', description='Doctor of Philosophy') p.save() p = GradProgram(unit=cmpt, label='Qualifying', description='Qualifying student') p.save() p = GradProgram(unit=cmpt, label='Special', description='Special Arrangements') p.save() gr = GradRequirement(program=p, description='Achieved Speciality') gr.save() for p in GradProgram.objects.filter(unit=cmpt): gr = GradRequirement(program=p, description='Found campus') gr.save() gf = GradFlag(unit=cmpt, label='Dual Degree Program') gf.save() gf = GradFlag(unit=cmpt, label='Co-op') gf.save() grads = list(Person.objects.filter(last_name='Grad')) programs = list(GradProgram.objects.all()) today = datetime.date.today() starts = Semester.objects.filter(start__gt=today-datetime.timedelta(1000), start__lt=today) supervisors = list(set([m.person for m in Member.objects.filter(role='INST').select_related('person')])) # create GradStudents (and associated data) in a vaguely realistic way for g in grads + random.sample(grads, 5): # put a few in two programs p = random.choice(programs) start = random.choice(starts) sstart = start.start if random.randint(1,2) == 1: end = start.offset(random.randint(3,9)) else: end = None gs = GradStudent(person=g, program=p, research_area=randname(8)+'ology', campus=random.choice([x for x,_ in CAMPUS_CHOICES]), is_canadian=randnullbool()) gs.save() gph = GradProgramHistory(student=gs, program=p, start_semester=start) gph.save() if random.randint(1,3) == 1: p2 = random.choice([p2 for p2 in programs if p != p2 and p.unit == p2.unit]) gph = GradProgramHistory(student=gs, program=p2, start_semester=start.offset(random.randint(1,4))) gph.save() s = GradStatus(student=gs, status='COMP', start=start, start_date=sstart-datetime.timedelta(days=100)) s.save() if random.randint(1,4) == 1: s = GradStatus(student=gs, status='REJE', start=start, start_date=sstart-datetime.timedelta(days=80)) s.save() else: s = GradStatus(student=gs, status='OFFO', start=start, start_date=sstart-datetime.timedelta(days=80)) s.save() s = GradStatus(student=gs, status='ACTI', start=start, start_date=sstart) s.save() if end: if random.randint(1,3): s = GradStatus(student=gs, status='WIDR', start=end, start_date=end.start) s.save() else: s = GradStatus(student=gs, status='GRAD', start=end, start_date=end.start) s.save() gs.update_status_fields() # give some money sch = Scholarship(student=gs, scholarship_type=random.choice(scholarship_types)) sch.amount = 2000 sch.start_semester = start sch.end_semester = start.offset(2) sch.save() of = OtherFunding(student=gs, semester=start.offset(3)) of.amount = 1300 of.description = "Money fell from the sky" of.save() # promise p = Promise(student=gs, start_semester=start, end_semester=start.offset(2), amount=10000) p.save() p = Promise(student=gs, start_semester=start.offset(3), end_semester=start.offset(5), amount=10000) p.save() # flags if random.randint(1,3) == 1: cr = CompletedRequirement(requirement=gr, student=gs, semester=start.offset(1)) cr.save() if random.randint(1,4) == 1: gfv = GradFlagValue(flag=gf, student=gs, value=True) gfv.save() # supervisors if random.randint(1,3) != 1: p = random.choice(supervisors) s = Supervisor(student=gs, supervisor=p, supervisor_type='POT') s.save() if random.randint(1,2) == 1: s = Supervisor(student=gs, supervisor=p, supervisor_type='SEN') s.save() s = Supervisor(student=gs, supervisor=random.choice(supervisors), supervisor_type='COM') s.save() return itertools.chain( roles, programs, scholarship_types, GradRequirement.objects.all(), LetterTemplate.objects.all(), GradFlag.objects.all(), GradStudent.objects.all(), GradProgramHistory.objects.all(), GradStatus.objects.all(), Scholarship.objects.all(), OtherFunding.objects.all(), Promise.objects.all(), CompletedRequirement.objects.all(), GradFlagValue.objects.all(), Supervisor.objects.all(), )
def import_student( program_map, semester_object, dryrun, skip_duplicates, unit, emplid, adm_appl_nbr, acad_prog, errors, adm_appl_nbrs ): """ program_map - a dictionary, mapping SIMS indicators ("CPPHD") to GradProgram objects (GradProgram.objects.get(...)) semester - Semester dryrun - if True, do not actually import the student unit - Unit emplid - the emplid of the student to import adm_appl_nbr - admission application number of the student to import acad_prog - acad_prog of the student to import errors - array containing any errors encountered by the system so far adm_appl_nbrs - array containing any adm_appl_nbrs encountered by the system so far. """ print(emplid, adm_appl_nbr) # Find or generate a Person object for this student person = find_or_generate_person(emplid) # Do we already have this student? if is_already_imported( person, adm_appl_nbr ) or adm_appl_nbr in adm_appl_nbrs: print("This GradStudent record already exists in coursys") print(" -------------------------------- ") return errors, adm_appl_nbrs # This additional check shouldn't be necessary, a year or so from now. grad_student_records = GradStudent.objects.filter(person=person) if len(grad_student_records) > 0: print("This GradStudent record may already exist in coursys: ") if skip_duplicates: print(".. so we're not dealing with it for now.") return errors, adm_appl_nbrs else: print("Please select: ") for i in range(0, len(grad_student_records)): student = grad_student_records[i] print(i, "--", student, "--", "http://courses.cs.sfu.ca/grad/"+student.slug) print("N -- None of these are correct; Proceed with import.") n = get_number_or_n( list(range(0, len(grad_student_records))) ) if n != 'n': correct_record = grad_student_records[n] correct_record.config['adm_appl_nbr'] = adm_appl_nbr if not dryrun: correct_record.save() return errors, adm_appl_nbrs adm_appl_nbrs.append(adm_appl_nbr) # Find or generate a Program for this student try: program = program_map[acad_prog] except KeyError: errors.append( emplid + " was not imported" ) errors.append("\tThe program for " + acad_prog + " could not be found. This is a Bad Thing. Fix the program map.") return errors, adm_appl_nbrs print(acad_prog) print(program) english_fluency = "" mother_tongue = get_mother_tongue( emplid ) print(mother_tongue) passport_issued_by = get_passport_issued_by( emplid ) print(passport_issued_by) if passport_issued_by == "Canada": is_canadian = True elif holds_resident_visa( emplid ): is_canadian = True else: is_canadian = False print(is_canadian) research_area = get_research_area( emplid, program.unit.acad_org ) print(research_area) grad = GradStudent( person=person, program=program, english_fluency=english_fluency, mother_tongue=mother_tongue, is_canadian=is_canadian, research_area=research_area, passport_issued_by=passport_issued_by, comments="" ) grad.config['adm_appl_nbr'] = adm_appl_nbr grad.config['imported_from'] = "Grad student import " + str(datetime.date.today()) email = get_email(emplid) if email: grad.config['applic_email'] = email print("Creating new Grad Student") print(grad) if not dryrun: grad.save() # Personal data personal_info = coredata.queries.grad_student_info(emplid) print(personal_info) if 'visa' in personal_info: person.config['visa'] = personal_info['visa'] if 'citizen' in personal_info: person.config['citizen'] = personal_info['citizen'] if 'ccredits' in personal_info: person.config['ccredits'] = personal_info['ccredits'] if 'gpa' in personal_info: person.config['gpa'] = personal_info['gpa'] if 'gender' in personal_info: person.config['gender'] = personal_info['gender'] # GradProgramHistory history = GradProgramHistory( student=grad, program=program, start_semester=semester_object, starting=semester_object.start ) # GradStatus chronological_history = get_status_history( emplid, adm_appl_nbr) admitted = False grad_statuses = [] for event, date, semester in chronological_history: status_code = None if event == "ADMT" or event == "COND": status_code = "OFFO" admitted = True elif event == "APPL": status_code = "COMP" elif event == "MATR": status_code = "CONF" elif event == "DENY": status_code = "REJE" elif event == "WAPP" or event == "WADM": if admitted: status_code = "DECL" else: status_code = "EXPI" else: print("Status " + event + " cannot be converted into a status.") continue start_semester = Semester.objects.get(name=semester) status = GradStatus( student=grad, status=status_code, start=start_semester, start_date=date ) print(status) grad_statuses.append( status ) # Save all of the actual data. if not dryrun: person.save() history.save() for status in grad_statuses: status.save() print("------------------") return errors, adm_appl_nbrs
def import_student(program_map, semester_object, dryrun, skip_duplicates, unit, emplid, adm_appl_nbr, acad_prog, errors, adm_appl_nbrs): """ program_map - a dictionary, mapping SIMS indicators ("CPPHD") to GradProgram objects (GradProgram.objects.get(...)) semester - Semester dryrun - if True, do not actually import the student unit - Unit emplid - the emplid of the student to import adm_appl_nbr - admission application number of the student to import acad_prog - acad_prog of the student to import errors - array containing any errors encountered by the system so far adm_appl_nbrs - array containing any adm_appl_nbrs encountered by the system so far. """ print(emplid, adm_appl_nbr) # Find or generate a Person object for this student person = find_or_generate_person(emplid) # Do we already have this student? if is_already_imported(person, adm_appl_nbr) or adm_appl_nbr in adm_appl_nbrs: print("This GradStudent record already exists in coursys") print(" -------------------------------- ") return errors, adm_appl_nbrs # This additional check shouldn't be necessary, a year or so from now. grad_student_records = GradStudent.objects.filter(person=person) if len(grad_student_records) > 0: print("This GradStudent record may already exist in coursys: ") if skip_duplicates: print(".. so we're not dealing with it for now.") return errors, adm_appl_nbrs else: print("Please select: ") for i in range(0, len(grad_student_records)): student = grad_student_records[i] print(i, "--", student, "--", "http://courses.cs.sfu.ca/grad/" + student.slug) print("N -- None of these are correct; Proceed with import.") n = get_number_or_n(list(range(0, len(grad_student_records)))) if n != 'n': correct_record = grad_student_records[n] correct_record.config['adm_appl_nbr'] = adm_appl_nbr if not dryrun: correct_record.save() return errors, adm_appl_nbrs adm_appl_nbrs.append(adm_appl_nbr) # Find or generate a Program for this student try: program = program_map[acad_prog] except KeyError: errors.append(emplid + " was not imported") errors.append( "\tThe program for " + acad_prog + " could not be found. This is a Bad Thing. Fix the program map.") return errors, adm_appl_nbrs print(acad_prog) print(program) english_fluency = "" mother_tongue = get_mother_tongue(emplid) print(mother_tongue) passport_issued_by = get_passport_issued_by(emplid) print(passport_issued_by) if passport_issued_by == "Canada": is_canadian = True elif holds_resident_visa(emplid): is_canadian = True else: is_canadian = False print(is_canadian) research_area = get_research_area(emplid, program.unit.acad_org) print(research_area) grad = GradStudent(person=person, program=program, english_fluency=english_fluency, mother_tongue=mother_tongue, is_canadian=is_canadian, research_area=research_area, passport_issued_by=passport_issued_by, comments="") grad.config['adm_appl_nbr'] = adm_appl_nbr grad.config['imported_from'] = "Grad student import " + str( datetime.date.today()) email = get_email(emplid) if email: grad.config['applic_email'] = email print("Creating new Grad Student") print(grad) if not dryrun: grad.save() # Personal data personal_info = coredata.queries.grad_student_info(emplid) print(personal_info) if 'visa' in personal_info: person.config['visa'] = personal_info['visa'] if 'citizen' in personal_info: person.config['citizen'] = personal_info['citizen'] if 'ccredits' in personal_info: person.config['ccredits'] = personal_info['ccredits'] if 'gpa' in personal_info: person.config['gpa'] = personal_info['gpa'] if 'gender' in personal_info: person.config['gender'] = personal_info['gender'] # GradProgramHistory history = GradProgramHistory(student=grad, program=program, start_semester=semester_object, starting=semester_object.start) # GradStatus chronological_history = get_status_history(emplid, adm_appl_nbr) admitted = False grad_statuses = [] for event, date, semester in chronological_history: status_code = None if event == "ADMT" or event == "COND": status_code = "OFFO" admitted = True elif event == "APPL": status_code = "COMP" elif event == "MATR": status_code = "CONF" elif event == "DENY": status_code = "REJE" elif event == "WAPP" or event == "WADM": if admitted: status_code = "DECL" else: status_code = "EXPI" else: print("Status " + event + " cannot be converted into a status.") continue start_semester = Semester.objects.get(name=semester) status = GradStatus(student=grad, status=status_code, start=start_semester, start_date=date) print(status) grad_statuses.append(status) # Save all of the actual data. if not dryrun: person.save() history.save() for status in grad_statuses: status.save() print("------------------") return errors, adm_appl_nbrs
def import_student( emplid, gradprogram, semester_string, dryrun=True ): """ Import student with emplid into gradprogram, using as much SIMS data as possible. """ person = find_or_generate_person(emplid) program = gradprogram print(person, program) english_fluency = "" mother_tongue = get_mother_tongue( emplid ) print(mother_tongue) passport_issued_by = get_passport_issued_by( emplid ) print(passport_issued_by) if passport_issued_by == "Canada": is_canadian = True elif holds_resident_visa( emplid ): is_canadian = True else: is_canadian = False print("Canadian: ", is_canadian) research_area = get_research_area( emplid, program.unit.acad_org ) print(research_area) grad = GradStudent( person=person, program=program, english_fluency=english_fluency, mother_tongue=mother_tongue, is_canadian=is_canadian, research_area=research_area, passport_issued_by=passport_issued_by, comments="" ) grad.config['imported_from'] = "MSE special import " + str(datetime.date.today()) email = get_email(emplid) if email: grad.config['applic_email'] = email print("Creating new Grad Student") print(grad) if not dryrun: grad.save() # Personal data personal_info = coredata.queries.grad_student_info(emplid) print(personal_info) if 'visa' in personal_info: person.config['visa'] = personal_info['visa'] if 'citizen' in personal_info: person.config['citizen'] = personal_info['citizen'] if 'ccredits' in personal_info: person.config['ccredits'] = personal_info['ccredits'] if 'gpa' in personal_info: person.config['gpa'] = personal_info['gpa'] if 'gender' in personal_info: person.config['gender'] = personal_info['gender'] try: semester_object = Semester.objects.get(name=semester_string) except Semester.DoesNotExist: print("Semester " + name + " does not exist") return # GradProgramHistory history = GradProgramHistory( student=grad, program=program, start_semester=semester_object, starting=semester_object.start ) print(history) status = GradStatus(student=grad, status='ACTI', start=semester_object) print(status) # Save all of the actual data. if not dryrun: person.save() history.save() status.save() print("------------------")
def process_pcs_row(row, column, rownum, unit, semester, user): """ Process a single row from the PCS import """ appsemester = semester.previous_semester() warnings = [] ident = "in row %i" % (rownum) appid = row[column['appid']] emplid = row[column['emplid']] program = row[column['program']] # get Person, from SIMS if necessary try: p = Person.objects.get(emplid=int(emplid)) except ValueError: warnings.append("Bad emplid %s: not processing that row." % (ident)) return warnings except Person.DoesNotExist: try: p = add_person(emplid) except SIMSProblem as e: return str(e) ident = 'for "%s"' % (p.name()) # update information on the Person email = row[column['email']] if email: p.config['applic_email'] = email dob = row[column['dob']] if dob: try: dt = datetime.datetime.strptime(dob, "%Y-%m-%d") p.config['birthdate'] = dt.date().isoformat() except ValueError: warnings.append("Bad birthdate %s." % (ident)) # get extended SIMS data data = grad_student_info(emplid) p.config.update(data) p.save() #print "Importing %s" % (p) # get GradStudent, creating if necessary # a unique identifier for this application, so we can detect repeated imports (and handle gracefully) uid = "%s-%s-%s-%s" % (unit.slug, semester.name, appid, emplid) # TODO: wrong, wrong, wrong. Figure out how to select program from import data program = GradProgram.objects.filter(unit=unit)[0] # find the old GradStudent if possible gss = GradStudent.objects.filter(program__unit=unit, person=p) gs = None for g in gss: if 'app_id' in g.config and g.config['app_id'] == uid: gs = g break if not gs: gs = GradStudent(program=program, person=p) gs.config['app_id'] = uid resarea = row[column['resarea']] firstlang = row[column['firstlang']] gs.research_area = resarea gs.mother_tongue = firstlang gs.created_by = user.userid gs.updated_by = user.userid gs.config['start_semester'] = semester.name gs.save() complete = row[column['complete']].strip() decision = row[column['decision']].strip() notes = row[column['notes']].strip() gs.config['decisionnotes'] = notes old_st = GradStatus.objects.filter(student=gs, start__name__gte=semester.name) if not old_st: # if no old status for current semester, create one # application completion status if complete == 'AppConfirm': st = GradStatus(student=gs, status="COMP", start=appsemester, end=None, notes="PCS import") st.save() elif complete == '': st = GradStatus(student=gs, status="INCO", start=appsemester, end=None, notes="PCS import") st.save() else: warnings.append('Unknown "Confirmation of Completion of Application" value %s.' % (ident)) # decision status if decision == 'DECL': st = GradStatus(student=gs, status="DECL", start=appsemester, end=None, notes="PCS import") st.save() elif decision == '': st = GradStatus(student=gs, status="OFFO", start=appsemester, end=None, notes="PCS import") st.save() elif decision == 'R': st = GradStatus(student=gs, status="REJE", start=appsemester, end=None, notes="PCS import") st.save() elif decision == 'HOLD': st = GradStatus(student=gs, status="HOLD", start=appsemester, end=None, notes="PCS import") st.save() elif decision == 'AMScT': # TODO: bump program to MSc thesis st = GradStatus(student=gs, status="CONF", start=appsemester, end=None, notes="PCS import") st.save() elif decision == 'AMScC': # TODO: bump program to MSc course-based st = GradStatus(student=gs, status="CONF", start=appsemester, end=None, notes="PCS import") st.save() # potential supervisor potsuper = row[column['potsuper']] if potsuper: superv = None external = None try: ps_last, ps_first = potsuper.split(', ') except ValueError: warnings.append('Bad potential supervisor name %s: will store them as an "external" supervisor.' % (ident)) external = potsuper else: potentials = possible_supervisor_people([unit]) potential_ids = [p.id for p in potentials] query = Q(last_name=ps_last, first_name=ps_first) | Q(last_name=ps_last, pref_first_name=ps_first) people = Person.objects.filter(query, id__in=potential_ids) if people.count() == 1: superv = people[0] else: warnings.append('Coundn\'t find potential supervisor %s: will store them as an "external" supervisor.' % (ident)) external = potsuper old_s = Supervisor.objects.filter(student=gs, supervisor_type='POT') if old_s: s = old_s[0] else: s = Supervisor(student=gs, supervisor_type='POT') s.superv = superv s.external = external s.position = 0 s.created_by = user.userid s.modified_by = user.userid s.save() l = LogEntry(userid=user.userid, description="Imported grad record for %s (%s) from PCS" % (p.name(), p.emplid), related_object=gs) l.save() return warnings
def import_student(emplid, gradprogram, semester_string, dryrun=True): """ Import student with emplid into gradprogram, using as much SIMS data as possible. """ person = find_or_generate_person(emplid) program = gradprogram print person, program english_fluency = "" mother_tongue = get_mother_tongue(emplid) print mother_tongue passport_issued_by = get_passport_issued_by(emplid) print passport_issued_by if passport_issued_by == "Canada": is_canadian = True elif holds_resident_visa(emplid): is_canadian = True else: is_canadian = False print "Canadian: ", is_canadian research_area = get_research_area(emplid, program.unit.acad_org) print research_area grad = GradStudent(person=person, program=program, english_fluency=english_fluency, mother_tongue=mother_tongue, is_canadian=is_canadian, research_area=research_area, passport_issued_by=passport_issued_by, comments="") grad.config['imported_from'] = "MSE special import " + str( datetime.date.today()) email = get_email(emplid) if email: grad.config['applic_email'] = email print "Creating new Grad Student" print grad if not dryrun: grad.save() # Personal data personal_info = coredata.queries.grad_student_info(emplid) print personal_info if 'visa' in personal_info: person.config['visa'] = personal_info['visa'] if 'citizen' in personal_info: person.config['citizen'] = personal_info['citizen'] if 'ccredits' in personal_info: person.config['ccredits'] = personal_info['ccredits'] if 'gpa' in personal_info: person.config['gpa'] = personal_info['gpa'] if 'gender' in personal_info: person.config['gender'] = personal_info['gender'] try: semester_object = Semester.objects.get(name=semester_string) except Semester.DoesNotExist: print "Semester " + name + " does not exist" return # GradProgramHistory history = GradProgramHistory(student=grad, program=program, start_semester=semester_object, starting=semester_object.start) print history status = GradStatus(student=grad, status='ACTI', start=semester_object) print status # Save all of the actual data. if not dryrun: person.save() history.save() status.save() print "------------------"