def __init__(self):
        """
        Does the work of reading in basic information from file, creates native Python structures
        StudentNumber\tHomeroom\tLastFirst\tguardianemails
        """
        self.settings = define_command_line_arguments(
                               'verbose', 'log_level', 'dry_run', 'teachers', 'courses',
                               'students', 'email_list', 'families', 'parents',
                               'automagic_emails', 'profiles', 'input_okay', 'updaters',
                               'enroll_cohorts', 'enroll_courses', 'remove_enrollments', inspect_student=False)

        self.logger = logging.getLogger(self.__class__.__name__)
        self.path_to_powerschool = config_get_section_attribute('DIRECTORIES', 'path_to_powerschool_dump')
        self.path_to_output = config_get_section_attribute('DIRECTORIES', 'path_to_output')
        self.student_info_file = AutoSendFile('dist', 'studentinfo')
        self.student_info_controller = Controller(Student)
        self.course_info_controller = Controller(Course)
        self.teacher_info_controller = Controller(Teacher)
        self.schedule_info_controller = Controller(Schedule)
        self.allocation_info_controller = Controller(Allocation)
        self.user_data = ServerInfo().get_student_info()
        self.read_in()
        homerooms = None
        self._secondary_homerooms = None
        self._elementary_homerooms = None
        self.get_homerooms()
        self.get_secondary_homerooms()
class Tree:

    exclude_these_teachers_manually = ['Sections, Dead', 'User, Drews Test']

    def __init__(self):
        """
        Does the work of reading in basic information from file, creates native Python structures
        StudentNumber\tHomeroom\tLastFirst\tguardianemails
        """
        self.settings = define_command_line_arguments(
                               'verbose', 'log_level', 'dry_run', 'teachers', 'courses',
                               'students', 'email_list', 'families', 'parents',
                               'automagic_emails', 'profiles', 'input_okay', 'updaters',
                               'enroll_cohorts', 'enroll_courses', 'remove_enrollments', inspect_student=False)

        self.logger = logging.getLogger(self.__class__.__name__)
        self.path_to_powerschool = config_get_section_attribute('DIRECTORIES', 'path_to_powerschool_dump')
        self.path_to_output = config_get_section_attribute('DIRECTORIES', 'path_to_output')
        self.student_info_file = AutoSendFile('dist', 'studentinfo')
        self.student_info_controller = Controller(Student)
        self.course_info_controller = Controller(Course)
        self.teacher_info_controller = Controller(Teacher)
        self.schedule_info_controller = Controller(Schedule)
        self.allocation_info_controller = Controller(Allocation)
        self.user_data = ServerInfo().get_student_info()
        self.read_in()
        homerooms = None
        self._secondary_homerooms = None
        self._elementary_homerooms = None
        self.get_homerooms()
        self.get_secondary_homerooms()
        
    def get_homerooms(self):
        if not self._homerooms:
            self._homerooms = []
            for student_key in self.get_student_keys():
                student = self.get_student(student_key)
                if not student.homeroom in self._homerooms:
                    self._homerooms.append(student.homeroom)
        self._homerooms.sort(key=put_in_order)
        return self._homerooms

    def student_keys_by_secondary_homerooms(self):
        for hr in self.get_secondary_homerooms():
            for student_key in self.get_secondary_student_keys():
                student = self.get_student(student_key)
                if student.homeroom == hr:
                    yield student

    def get_secondary_homerooms(self):
        if not self._secondary_homerooms:
            self._secondary_homerooms = []
            for student_key in self.get_student_keys():
                student = self.get_student(student_key)
                if student.is_secondary:
                    if not student.homeroom in self._secondary_homerooms:
                        self._secondary_homerooms.append(student.homeroom)
        self._secondary_homerooms.sort(key=put_in_order)
        return self._secondary_homerooms

    def get_elementary_homerooms(self):
        if not self._elementary_homerooms:
            self._elementary_homerooms = []
            for student_key in self.get_elementary_student_keys():
                student = self.get_student(student_key)
                if student.is_elementary:
                    if not student.homeroom in self._elementary_homerooms:
                        self._elementary_homerooms.append(student.homeroom)
        return self._elementary_homerooms
    
    def get_all_groups(self):
        list_of_groups = []
        for key in self.get_student_keys():
            student = self.get_student(key)
            groups = student.groups()
            for group in groups:
                if not group in list_of_groups:
                    list_of_groups.append(group)
        return list_of_groups

    def get_all_grades(self):
        list_of_grades = []
        for key in self.get_student_keys():
            student = self.get_student(key)
            grade = student.grade
            if not grade in list_of_grades:
                list_of_grades.append(grade)
        return list_of_grades

    def read_in(self):
        """
        Parses file into pythonic data types, called at construction
        Reads in other information as well
        """
        for line in self.student_info_file.content():
            # This MUST sync with AutoSend
            try:
                stunum, stuid, grade, homeroom, firstlast, DOB, parent_emails, entry_date, nationality = line
            except ValueError:
                self.logger.warn(line)
                self.logger.warn("Skipping above line... did one of the fields have a newline character in there?")
                continue

            if not homeroom:
                self.logger.warn('This student does not have a homeroom {}: {}'.format(stunum, firstlast))
            
            try:
                grade = int(grade)
            except ValueError:
                self.logger.warn('This student has a non-integer grade {} {}: {}'.format(stunum, firstlast, grade))
                grade = 0
            new_student = self.add(stunum,
                stuid, grade,
                homeroom,
                self.convert_hr_to_sortable(homeroom),
                firstlast,DOB,                                   
                re.split('[;,]', parent_emails),
                datetime.datetime.strptime(entry_date, '%m/%d/%Y'),
                nationality,
                user_data=self.user_data)

        self.read_in_others()
        self.sync_others()

    def read_in_others(self):
        if self.settings.courses:
            self.read_in_courses()
            self.read_in_schedule()
        if self.settings.teachers:
            self.read_in_teachers()
            self.read_in_allocations()

    def read_in_preferred(self):
        self.preferred_temp = {}
        with open('preferred_names.txt') as pn:
            raw = pn.readlines()
            for line in raw:
                line = line.strip('\n')
                if not line.strip(): continue
                num, lastfirst, homeroom, first, last = line.strip('\n').split('\t')
                num = num.strip()
                try:
                    self.preferred_temp[num] = (first, last)
                except KeyError:
                    input(num)

    def sync_others(self):
        """ Round robin """
        #self.sync_preferred()   # this one to get the names right
        if self.settings.courses:
            self.sync_courses()     # this one for reference
            self.sync_schedule()    # this one to export student data
        if self.settings.teachers:
            self.sync_teachers()    # this one to export teacher data
            self.sync_allocations() # copy to teachers

    def sync_preferred(self):
        """
        Get the names right, modify the student data to compensate for PowerSchool's lack
        """
        for student in self.preferred_temp.keys():
            target = student
            source = self.preferred_temp[student]
            try:
                self.update(target, 'preferred', source)
            except NoSuchStudent:
                pass

    def read_in_courses(self):
        for line in AutoSendFile('sec', 'courseinfo').content():
            course_number, full_name = line
            moodle_short, moodle_long = convert_short_long(course_number, full_name)
            self.add_course(course_number, full_name, moodle_short, moodle_long)
            
    def sync_courses(self):
        """
        No syncing necessary, this one is for reference
        """
        pass

    def read_in_allocations(self):
        allocations = AutoSendFile('sec', 'teacherallocations')
        raw = allocations.content()
        self.allocation_table = defaultdict(list)
        for line in raw:
            course_number, course_name, teacher_name, status, termID = line
            teacher = self.teacher_info_controller.get(teacher_name)
            if not teacher:
                self.logger.info("No teacher by this name?: {}".format(teacher_name))
                continue
            self.allocation_table[course_number].append(self.teacher_info_controller.get(teacher_name))

    def sync_allocations(self):
        self.logger.info("Syncing teachers")
        for allocation in self.allocation_table.keys():
            for teacher in self.allocation_table[allocation]:
                if not teacher: 
                    self.logger.warn("no teacher?")
                    self.logger.warn(allocation)
                    self.logger.warn(self.allocation_table[allocation])
                    continue
                teacher = self.teacher_info_controller.get(teacher.lastfirst)
                course = self.course_info_controller.get(allocation)
                if course and teacher:
                    self.logger.debug("Syncing teacher {} with course {}".format(teacher, course))
                    course.update_teachers(teacher)
                    teacher.update_courses(course)

    def read_in_teachers(self):
        teachers = AutoSendFile('dist', 'staffinfo')
        raw = teachers.content()
        for line in raw:
            try:
                num, lastfirst, email, title, schoolid, staff_status = line
            except ValueError:
                self.logger.info("This teacher wasn't added to database: {}".format(line))
                continue
            if 1 == int(staff_status):
                self.add_teacher(lastfirst, num, email, title, staff_status, schoolid)

    def sync_teachers(self):
        """
        Create associations required: Students they have, courses they have
        """
        pass

    def read_in_schedule(self):
        schedule = AutoSendFile('sec', 'studentschedule')
        raw = schedule.content()
        self.schedule = defaultdict(list)
        for line in schedule.content():
            course_number, periods, session_number, teacher, teacherID, student, studentID = line
            self.schedule[course_number].append((teacher, studentID))

    def sync_schedule(self):
        """
        Put courses and teachers into student data, so they can be exported
        """
        self.logger.info("Syncing schedule information")
        for key in self.schedule.keys():
            for row in self.schedule[key]:
                teacher_lastfirst, studentID = row
                course = self.course_info_controller.get(key)
                teacher = self.teacher_info_controller.get(teacher_lastfirst)
                student = self.student_info_controller.get(studentID)
                if not student:
                    self.logger.warn("sync_schedule problem: {}".format(row))
                    continue
                if teacher and student:
                    self.logger.debug("Syncing teacher {} with student {}".format(teacher, student))
                    teacher.update_students(student)
                if student and course and teacher:
                    self.logger.debug("Syncing student {} with teacher {} with course {}".format(student, teacher, course))
                    student.update_teachers(course, teacher)
                    student.update_courses(course, teacher)

    def convert_hr_to_integer(self, hr):
        return {'K':-1, 'R':-2, 'G':-3, 'P':-4, 'N':-5}.get(hr[0], put_in_order(hr, reverse=True))
 
    def convert_hr_to_grade(self, hr):
        if not hr: raise NoHomeroom(hr)
        if not re.match(r'^[a-zA-Z]', hr):
            return int(re.sub(r'[a-zA-Z]', '', hr))
        else:
            hr = hr.upper()
            result = {'K':-1, 'R':-2, 'G':-3, 'P':-4, 'N':-5}.get(hr[0], None)
            if result == None: raise UnknownGrade(hr)
            return result

    def convert_hr_to_sortable(self, hr):
        return 0 #TODO: Use put_in_order, right?
        return self.convert_hr_to_grade(hr) + (1 / self.convert_hr_to_integer(hr))

    def add(self, *args, **kwargs):
        """
        Takes data and sends it on to controller
        """
        return self.student_info_controller.add(*args, **kwargs)

    def add_course(self, *args, **kwargs):
        self.course_info_controller.add(*args, **kwargs)

    def add_teacher(self, *args, **kwargs):
        self.teacher_info_controller.add(*args, **kwargs)

    def add_allocation(self, *args, **kwargs):
        self.allocation_info_controller.add(*args, **kwargs)

    def add_schedule(self, *args, **kwargs):
        self.schedule_info_controller.add(*args, **kwargs)

    def update(self, item, key, value):
        self.student_info_controller.update(item, key, value)

    # The following messages are meant to be for convenience and testing

    def output(self, *args, **kwargs):
        self.student_info_controller.output()

    def output_filter(self, *args, **kwargs):
        self.student_info_controller.output_filter(*args, **kwargs)

    def courses_output(self):
        self.course_info_controller.output()

    def students_output(self):
        self.student_info_controller.output()

    def teachers_output(self):
        self.teacher_info_controller.output()

    def get_course_keys(self):
        return list(self.course_info_controller.keys())

    def get_course(self, course_id):
        return self.course_info_controller.get(course_id)

    def get_student(self, student_id):
        return self.student_info_controller.get(student_id)

    def get_family(self, idnumber):
        if len(idnumber) == 5:
            idnumber = idnumber[:4]
        students = []
        for num in range(0, 9):
            student = self.get_student(idnumber + str(num))
            if student:
                students.append(student)
        return students

    def get_all_student_keys(self):
        return list(self.student_info_controller._db.keys())

    def get_students_by_family_id(self, family_id):
        info = self.student_info_controller
        return [info.get_student(student_key) for student_key in info.get_student_keys()
                if info.get_student(student_key).family_id == family_id]

    def get_family_emails(self, family_id):
        emails = []
        for student in self.get_students_by_family_id(family_id):
            emails.extend( student.parent_emails )
        return set(emails)

    def get_student_keys(self, secondary=False):
        """
        Returns all of them.
        """
        if secondary:
            return [s for s in list(self.student_info_controller.keys()) if self.student_info_controller.get(s).is_secondary]
        else:
            return list(self.student_info_controller.keys())

    def get_secondary_student_keys(self):
        return self.get_student_keys(secondary=True)

    def get_elementary_student_keys(self):
        return [s for s in list(self.student_info_controller.keys()) if self.student_info_controller.get(s).is_elementary]

    def get_teacher_keys(self):
        return list(self.teacher_info_controller.keys())

    def get_teacher(self, teacher_id):
        return self.teacher_info_controller.get(teacher_id)

    def get_students_filter(self, **kwargs):
        l = list(self.student_info_controller.keys())
        for key in self.student_info_controller.keys():
            this = self.get_student(key)
            if not this:
                continue
            if this.compare_kwargs(**kwargs):
                yield this