def check_rooms(self, exclude=[]): """ Check for repetetive rooms, ie. when one room is occupied simultanously by two lessons. :param exclude: You can exclude specific room, eg. gym. :type excelud: :class:`list` of :class:`int` """ stmt = Session.query(Lesson.room, Lesson.day, Lesson.order, Lesson.schedule_id) stmt = stmt.group_by(Lesson.room, Lesson.order, Lesson.day, Lesson.schedule_id) stmt = stmt.having(func.count(Lesson.room) > 1) stmt = stmt.filter(not_(Lesson.room.in_(exclude))) stmt = stmt.subquery() q = Session.query(Lesson).join( (stmt, and_(Lesson.room == stmt.c.room, Lesson.day == stmt.c.day, Lesson.order == stmt.c.order, Lesson.schedule_id == stmt.c.schedule_id))) q = q.order_by(Lesson.day, Lesson.order, Lesson.room) conflicts = q.all() if len(conflicts) == 0: return [] rooms = [[conflicts.pop(0), conflicts.pop(0)]] for c in conflicts: prev = rooms[-1][-1] if c.room == prev.room and c.day == prev.day and c.order == \ prev.order and c.schedule_id == prev.schedule_id: rooms[-1].append(c) else: rooms.append([c]) return rooms
def now(self, surname): """ Get the lesson for given person ``surname``. """ current_order = self.current_order() c.lesson = None if current_order is None: return render('now/now.xml') today = datetime.weekday(datetime.today()) schedule = Schedule.current() c.year = schedule.year students = Session.query(Student).\ filter(Student.last_name.like(surname)).all() teachers = Session.query(Educator).\ filter(Educator.last_name.like(surname)).all() people = students + teachers if len(people) != 1: c.people = people return render('now/list.xml') c.lesson = people[0].lesson(today, current_order, schedule.id) return render('now/now.xml')
def check_rooms(self, exclude=[]): """ Check for repetetive rooms, ie. when one room is occupied simultanously by two lessons. :param exclude: You can exclude specific room, eg. gym. :type excelud: :class:`list` of :class:`int` """ stmt = Session.query(Lesson.room, Lesson.day, Lesson.order, Lesson.schedule_id) stmt = stmt.group_by(Lesson.room, Lesson.order, Lesson.day, Lesson.schedule_id) stmt = stmt.having(func.count(Lesson.room)>1) stmt = stmt.filter(not_(Lesson.room.in_(exclude))) stmt = stmt.subquery() q = Session.query(Lesson).join((stmt, and_( Lesson.room == stmt.c.room, Lesson.day == stmt.c.day, Lesson.order == stmt.c.order, Lesson.schedule_id == stmt.c.schedule_id))) q = q.order_by(Lesson.day, Lesson.order, Lesson.room) conflicts = q.all() if len(conflicts) == 0: return [] rooms = [[conflicts.pop(0), conflicts.pop(0)]] for c in conflicts: prev = rooms[-1][-1] if c.room == prev.room and c.day == prev.day and c.order == \ prev.order and c.schedule_id == prev.schedule_id: rooms[-1].append(c) else: rooms.append([c]) return rooms
def new(self, format='html'): """GET /substitutions/new: Form to create a new item""" # url('new_substitution') c.date = self._closest_working_day(datetime.date.today()) c.groups = Session.query(Group).join(Group.year).\ order_by(desc(SchoolYear.start), Group.name).all() c.educators = Session.query(Educator).\ order_by(Educator.last_name).all() c.year = SchoolYear.current() return render('substitutions/new.xml')
def delete(self, id): """DELETE /substitutions/id: Delete an existing item""" # Forms posted to this method should contain a hidden field: # <input type="hidden" name="_method" value="DELETE" /> # Or using helpers: # h.form(url('substitution', id=ID), # method='delete') # url('substitution', id=ID) s = Session.query(Substitution).get(id) Session.delete(s) Session.commit() redirect(url('substitutions'))
def teacher_lesson(self): """Get teacher's scheduled lesson.""" day = datetime.date.weekday(self.date) query = Session.query(Lesson).filter_by(day=day, order=self.order, teacher_id=self.teacher.id) return query.all()
def all(self): """ Render all lucky numbers. """ c.numbers = Session.query(LuckyNumber).order_by(LuckyNumber.date).all() return render('lucky/list.xml')
def teacher(self, teacher_name, day_name=None): """ Render teacher's schedule for the given day. :param teacher_name: Surname of the teacher :param day_name: Name of the day """ day = self._translate_weekday(day_name) if day is None: return 'Bad day!' try: teacher = Session.query(Educator).\ filter(Educator.last_name.like(teacher_name)).one() except MultipleResultsFound: # TODO: how to do that properly? return """Hey, too much teachers with given surname were found. Please report it to administrator!""" except NoResultFound: return "No such teacher!" schedule = Schedule.current() c.teacher = teacher c.year = schedule.year c.lessons = teacher.schedule_for_day(day, schedule.id) return render('schedule/teacher.xml')
def current_week(cls, change_hour, now=None): """ Return ``current week``'s lucky numbers. ``current week`` is defined based on the given ``now`` and ``change_hour`` If there are no more lucky numbers in the ``current week`` fetch lucky numbers from the next available week. :param change_hour: The hour that defines the end of the day. :type change_hour: :class:`int` :param now: One of the ``current week``'s days. If value is set to None it will use real current datetime. :type now: :class:`datetime.datetime` or :class:`NoneType` """ if now is None: now = datetime.datetime.now() if now.hour >= change_hour: closest_day = now.date() + datetime.timedelta(1) else: closest_day = now.date() closest_weekday = datetime.date.weekday(closest_day) # Retrieve the date of the first day in the week start_date = closest_day - datetime.timedelta(closest_weekday) first_week_end = start_date + datetime.timedelta(7) second_week_end = start_date + datetime.timedelta(14) q = Session.query(cls).filter(cls.date >= start_date) # Optmization. If the ``closest_day`` is 0 it is already new week, # no need to query next two weeks, only one. if closest_weekday == 0: q = q.limit(7) else: q = q.limit(14) # Fetch numbers from the database numbers = q.all() first_week = [] second_week = [] for number in numbers: if number.date < first_week_end: first_week.append(number) else: if len(second_week) == 0: second_week_end = number.date + datetime.timedelta(7) if number.date < second_week_end: second_week.append(number) # Check whether first week any more lucky numbers relatively to # the current closest day. if len(first_week) > 0 and first_week[-1].date >= closest_day: return first_week else: return second_week
def current(cls, change_hour, now=None): """ Return current lucky number. If the current hour is less than ``change_hour`` try to fetch lucky number for the same date. Otherwise fetch the closest lucky number. :param change_hour: The hour that defines the end of the day. :type change_hour: :class:`int` :param now: Fetch lucky number relatively to given datetime. If value is set to None it will use real current datetime. :type now: :class:`datetime.datetime` or :class:`NoneType` """ if now is None: now = datetime.datetime.now() if now.hour >= change_hour: start_date = now.date() + datetime.timedelta(1) else: start_date = now.date() lucky = Session.query(cls).\ filter(cls.date >= start_date).\ order_by(cls.date).first() return lucky
def group_lesson(self): """Get group's scheduled lesson.""" day = datetime.date.weekday(self.date) query = Session.query(Lesson).\ filter_by(day=day, order=self.order, group_id=self.group_id). \ filter(not_(and_(Lesson.first_part == False, Lesson.second_part == False))) return query.all()
def date(self, date): """ Lucky number for the given date. :param date: Date in format: %Y-%m-%d """ date = date = datetime.datetime.strptime(date, '%Y-%m-%d') c.lucky = Session.query(LuckyNumber).filter_by(date=date).first() return render('lucky/current.xml')
def now_id(self, id): """ Get the lesson for given person ``id``. """ current_order = self.current_order() if current_order is None: c.lesson = None return render('now/now.xml') today = datetime.weekday(datetime.today()) schedule = Schedule.current() c.year = schedule.year person = Session.query(Student).get(id) if not person: person = Session.query(Educator).get(id) c.lesson = person.lesson(today, current_order, schedule.id) return render('now/now.xml')
def query_active(cls, schedule_id=None): """ Query educator with lessons in the schedule. """ q = Session.query(cls).outerjoin(Lesson).join(Schedule) if schedule_id is None: stmt = Schedule.query_current_id().subquery() q = q.join((stmt, Lesson.schedule_id == stmt.c.id)) else: q = q.filter(Schedule.id == schedule_id) return q
def left(cls): """ Get left lucky numbers, not used before, sorted. """ student_count = func.count(Student.id).label('student_count') recent_years = [sy.id for sy in SchoolYear.recent()] stmt = Session.query(student_count, Student).\ join(GroupMembership).\ join(Group).group_by(Group.id).\ filter(Group.year_id.in_(recent_years)).subquery() max = Session.query(func.max(stmt.c.student_count)).first()[0] count = Session.query(func.count(LuckyNumber.id)).first()[0] past = Session.query(LuckyNumber.number).\ order_by(desc(LuckyNumber.date)).\ limit(count % max).all() all = set(range(1, max + 1)) past = set(x[0] for x in past) left = list(all.difference(past)) left.sort() return left
def add(self): form_numbers = self.form_result['lucky'] numbers = [] for lucky in form_numbers: if lucky['date'] and lucky['number']: ln = LuckyNumber(lucky['date'], lucky['number']) numbers.append(ln) Session.add_all(numbers) try: Session.commit() except IntegrityError as e: session['flash'] = 'There is already lucky number for %s' % e.params[0] session.save() return redirect(url('lucky_add')) else: if len(numbers) == 0: session['flash'] = 'No lucky number has been added!' else: session['flash'] = 'Lucky numbers have been added!' session.save() return redirect(url('lucky_home'))
def query_started(cls, date=None): """ Query already started years and sort it by date from the newest to the oldest. :param date: query relatively to the given date. :type date: :class:`datetime.date` """ if date is None: date = func.date() q = Session.query(SchoolYear).\ filter(SchoolYear.start <= date).\ order_by(desc(SchoolYear.start)) return q
def query_current(cls, schedule_id=None): """ Query (ordered) lessons according to current schedule. Order by: day, order, part. """ q = Session.query(cls).\ order_by(cls.day, cls.order, desc(cls.first_part), desc(cls.second_part)) if schedule_id is None: stmt = Schedule.query_current_id().subquery() q = q.join((stmt, cls.schedule_id == stmt.c.id)) else: q = q.filter_by(schedule_id=schedule_id) return q
def teacher_week(self, teacher_name): """ Render teacher's weekly schedule. :param teacher_name: Last name of the teacher """ teachers = Session.query(Educator).\ filter(Educator.last_name.like(teacher_name)).all() if len(teachers) != 1: c.teachers = teachers return render('schedule/teacher/list.xml') schedule = Schedule.current() c.year = schedule.year c.teacher = teachers[0] c.schedule = c.teacher.schedule(schedule.id) return render('schedule/teacher/week.xml')
def by_full_name(self, full_name, relative_year=None): """ Return group by its full_name (index + name). Full name could be, eg. "1bch", "2inf1" or "2inf2". """ if len(full_name) < 1: return None try: index = int(full_name[0]) except ValueError: return None name = full_name[1:] year = SchoolYear.by_index(index, relative_year) group = Session.query(Group).filter_by(year=year, name=name).first() return group
def query_current(cls, year_id=None, date=None, q=None): """ Query current active schedule. :param year_id: School year of the Schedule :type year_id: :class:`int` :param date: If year_id is None method queries already started school years relatively to the given date :type date: :class:`datetime.date` """ if q is None: q = Session.query(Schedule) if year_id is None: stmt = SchoolYear.query_started(date).limit(1).subquery() q = q.join((stmt, Schedule.year_id == stmt.c.id)) else: q = q.filter_by(year_id=year_id) q = q.filter(Schedule.start <= func.date()) return q
def create(self): """POST /substitutions: Create a new item""" # url('substitutions') date = datetime.datetime.strptime(request.params['date'], '%Y-%m-%d') order = int(request.params['order']) group = Session.query(Group).get(int(request.params['group'])) part = int(request.params['part']) raw_educator = request.params['educator'] if raw_educator == 0: teacher = None else: teacher = Session.query(Educator).get(raw_educator) comment = request.params['comment'] s = Substitution(date, order, group, teacher, part, comment) Session.add(s) Session.commit() redirect(url('substitutions'))
def init_model(engine): """Call me before using any of the tables or classes in the model""" Session.configure(bind=engine)
def teacher_lesson(self): """Get teacher's scheduled lesson.""" day = datetime.date.weekday(self.date) query = Session.query(Lesson).filter_by( day=day, order=self.order, teacher_id=self.teacher.id) return query.all()
def index(self, format='html'): """GET /substitutions: All items in the collection""" # url('substitutions') c.subs = Session.query(Substitution).order_by(Substitution.date).all() return render('substitutions/list.xml')
def query_current_id(cls, year_id=None, date=None): q = Session.query(Schedule.id) return cls.query_current(year_id, date, q)
def table(self, date=None): """ Create a table of substitutions. This action tries to recreate table view as based on http://www.staszic.edu.pl/zastepstwa/. It gathers following data: 1. Which lessons do the group ussually have? 2. Which lessons do groups have in substitution? 3. Which groups (parts) are released (freed from the lesson) """ if date is None: date = self._closest_working_day(datetime.datetime.today()).date() else: date = datetime.datetime.strptime(date, '%Y-%m-%d').date() # Fetch substitutions from database q = Session.query(Substitution).filter_by(date=date) # Filter out pointless substitutions (those, which have both # parts set as False - no part is set) q = q.filter(not_(and_(Substitution.part1 == False, Substitution.part2 == False))) subs = q.all() # Create a dictionary of educators and their lessons by schedule # (before) and the new ones which are substitutes (after). before = {} after = {} # Create released groups dict: # keys are lesson orders and value is the list of released groups released = {} def fill(d, educator, order, fill): """ Helper function for filling the dict. It tries to fill as less data as possible so it merges parts intro groups. """ if not d.has_key(educator): d[educator] = {} e = d[educator] if not e.has_key(order): e[order] = [] o = e[order] g = fill[0] p = fill[1] if (g, None) in o: # We are trying to append part whereas entire group is present return if p is None: # We are appending entire group, delete "parted" entries for x in [1, 2]: try: o.remove((g, x)) except ValueError: pass o.append(fill) def opposite_substituted(subs, sub): """ Helper function for checking whether opposite group has been already substituted or the scheduled teacher is having a substituion which stops him from keeping the opposite group. It return True when both conditions are met: 1. Opposite group doesn't have any substituion on that hour and, 2. Scheduled teacher doesn't have any substition with other group. """ SUBSTITUTED = False EDUCATOR_FREE = True opposite_part = sub.part % 2 + 1 for s in subs: if s.order != sub.order: # We want only the same lesson order continue if s.group == sub.group and s.part == opposite_part: SUBSTITUTED = True break if s.teacher == lesson.teacher: EDUCATOR_FREE = False if not SUBSTITUTED and EDUCATOR_FREE: return True else: return False class ReleasedGroup(Exception): pass # Loop the substitutions for sub in subs: if sub.teacher is None: # Group is released for lesson in sub.group_lesson(): fill(before, lesson.teacher, lesson.order, (lesson.group, lesson.part)) if sub.part is not None and \ opposite_substituted(subs, sub): fill(after, lesson.teacher, lesson.order, (lesson.group, sub.part % 2 + 1)) if not released.has_key(sub.order): released[sub.order] = [] released[sub.order].append((sub.group, sub.part)) continue # What lesson does the group have normally? # And with which educator it is? for lesson in sub.group_lesson(): fill(before, lesson.teacher, lesson.order, (lesson.group, lesson.part)) if lesson.part is None and sub.part is not None: if opposite_substituted(subs, sub): fill(after, lesson.teacher, lesson.order, (lesson.group, sub.part % 2 + 1)) for lesson in sub.teacher_lesson(): fill(after, lesson.teacher, lesson.order, (lesson.group, lesson.part)) fill(after, sub.teacher, sub.order, (sub.group, sub.part)) c.debug = ("Substitutions for %s:\nbefore:\t\t%r\nafter:\t\t%r\n" + \ "released:\t%r") % (date, before, after, released) c.year = SchoolYear.current() c.date = date c.before = before c.after = after c.released = released return render('substitutions/table.xml')
def table(self, date=None): """ Create a table of substitutions. This action tries to recreate table view as based on http://www.staszic.edu.pl/zastepstwa/. It gathers following data: 1. Which lessons do the group ussually have? 2. Which lessons do groups have in substitution? 3. Which groups (parts) are released (freed from the lesson) """ if date is None: date = self._closest_working_day(datetime.datetime.today()).date() else: date = datetime.datetime.strptime(date, '%Y-%m-%d').date() # Fetch substitutions from database q = Session.query(Substitution).filter_by(date=date) # Filter out pointless substitutions (those, which have both # parts set as False - no part is set) q = q.filter( not_(and_(Substitution.part1 == False, Substitution.part2 == False))) subs = q.all() # Create a dictionary of educators and their lessons by schedule # (before) and the new ones which are substitutes (after). before = {} after = {} # Create released groups dict: # keys are lesson orders and value is the list of released groups released = {} def fill(d, educator, order, fill): """ Helper function for filling the dict. It tries to fill as less data as possible so it merges parts intro groups. """ if not d.has_key(educator): d[educator] = {} e = d[educator] if not e.has_key(order): e[order] = [] o = e[order] g = fill[0] p = fill[1] if (g, None) in o: # We are trying to append part whereas entire group is present return if p is None: # We are appending entire group, delete "parted" entries for x in [1, 2]: try: o.remove((g, x)) except ValueError: pass o.append(fill) def opposite_substituted(subs, sub): """ Helper function for checking whether opposite group has been already substituted or the scheduled teacher is having a substituion which stops him from keeping the opposite group. It return True when both conditions are met: 1. Opposite group doesn't have any substituion on that hour and, 2. Scheduled teacher doesn't have any substition with other group. """ SUBSTITUTED = False EDUCATOR_FREE = True opposite_part = sub.part % 2 + 1 for s in subs: if s.order != sub.order: # We want only the same lesson order continue if s.group == sub.group and s.part == opposite_part: SUBSTITUTED = True break if s.teacher == lesson.teacher: EDUCATOR_FREE = False if not SUBSTITUTED and EDUCATOR_FREE: return True else: return False class ReleasedGroup(Exception): pass # Loop the substitutions for sub in subs: if sub.teacher is None: # Group is released for lesson in sub.group_lesson(): fill(before, lesson.teacher, lesson.order, (lesson.group, lesson.part)) if sub.part is not None and \ opposite_substituted(subs, sub): fill(after, lesson.teacher, lesson.order, (lesson.group, sub.part % 2 + 1)) if not released.has_key(sub.order): released[sub.order] = [] released[sub.order].append((sub.group, sub.part)) continue # What lesson does the group have normally? # And with which educator it is? for lesson in sub.group_lesson(): fill(before, lesson.teacher, lesson.order, (lesson.group, lesson.part)) if lesson.part is None and sub.part is not None: if opposite_substituted(subs, sub): fill(after, lesson.teacher, lesson.order, (lesson.group, sub.part % 2 + 1)) for lesson in sub.teacher_lesson(): fill(after, lesson.teacher, lesson.order, (lesson.group, lesson.part)) fill(after, sub.teacher, sub.order, (sub.group, sub.part)) c.debug = ("Substitutions for %s:\nbefore:\t\t%r\nafter:\t\t%r\n" + \ "released:\t%r") % (date, before, after, released) c.year = SchoolYear.current() c.date = date c.before = before c.after = after c.released = released return render('substitutions/table.xml')
def last(cls): """ Return most recent lucky number. """ return Session.query(cls).order_by(desc(cls.date)).first()