class Checkin(Base): """checkin for event""" __tablename__ = 'checkin' authorizer_id = db.Column(db.Integer, db.ForeignKey('user.id')) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) event_id = db.Column(db.Integer, db.ForeignKey('event.id')) @property def authorizer(self): """authorizing user""" return User.query.get(self.authorizer_id)
class Membership(Base): """user membership in group""" __tablename__ = 'membership' user_id = db.Column(db.Integer, db.ForeignKey('user.id')) group_id = db.Column(db.Integer, db.ForeignKey('group.id')) role_id = db.Column(db.Integer, db.ForeignKey('group_role.id')) def signups(self): """Returns signups for this membership""" return Signup.query.join(Event).filter( Event.group_id==self.group_id, Signup.user_id==self.user_id, Signup.is_active==True).all() def save(self): """save membership""" if not Membership.query.filter( Membership.user_id == self.user_id, Membership.group_id == self.group_id ).one_or_none(): return super(Membership, self).save() return self @property def group(self): return Group.query.get(self.group_id) @property def user(self): return User.query.get(self.user_id) @property def role(self): return GroupRole.query.get(self.role_id)
class Signup(Base): """user signup for event""" __tablename__ = 'signup' user_id = db.Column(db.Integer, db.ForeignKey('user.id')) event_id = db.Column(db.Integer, db.ForeignKey('event.id')) role_id = db.Column(db.Integer, db.ForeignKey('event_role.id')) category = db.Column(db.String(50)) status = db.Column(db.String(50)) preference = db.Column(db.Integer) comment = db.Column(db.Text) @property def event(self): return Event.query.get(self.event_id) @property def user(self): return User.query.get(self.user_id) @property def role(self): return EventRole.query.get(self.role_id) @property def is_checked_in(self): return Checkin.query.filter_by( user_id=self.user_id, event_id=self.event_id).count() > 0 @property def num_check_ins(self): return Checkin.query.filter_by( user_id=self.user_id, event_id=self.event_id).count() @classmethod def from_csv_string(cls, string, override=False): """Import signups from csv""" try: reader = csv.reader(string.splitlines(), delimiter=',') headers = [s.strip() for s in next(reader)] for row in reader: if ''.join(row).strip() == '': continue data = dict(zip(headers, [s.strip() for s in row])) user_data = {} for k in list(data.keys()): if k.startswith('user_'): user_data[k[5:]] = data.pop(k) user = User.get_or_create(email=user_data['email'], data=user_data) event_id = data.pop('event_id', None) event_ids = data.pop('event_ids', None) if event_ids: event_ids = [int(s.strip()) for s in event_ids[1:-1].split('|') if s] elif event_id: event_ids = [event_id] else: raise UserWarning('Must specify an event_id or event_ids col.') for event_id in event_ids: event = Event.query.get(event_id) role = EventRole.query.filter_by(name=event.setting('role').value, event_id=event_id).one() data['is_active'] = True yield Signup.get_or_create(user_id=user.id, event_id=event_id, override=override, role_id=role.id, data=data) except sqlalchemy.exc.IntegrityError: raise UserWarning('Invalid event_id found. Check that all event_ids are associated with valid events.')
class Event(Base): """PIAP event""" __tablename__ = 'event' __settingclass__ = EventSetting __defaultsettings__ = default_event_settings name = db.Column(db.Text, nullable=False) description = db.Column(db.Text) start = db.Column(ArrowType) end = db.Column(ArrowType) group_id = db.Column(db.Integer, db.ForeignKey('group.id')) parent_id = db.Column(db.Integer, db.ForeignKey('event.id')) google_id = db.Column(db.String(50), unique=True) settings = relationship("EventSetting", backref="event") checkins = relationship("Checkin", backref="event") frequency = db.Column(db.String(20)) on_mondays = db.Column(db.Boolean) on_tuesdays = db.Column(db.Boolean) on_wednesdays = db.Column(db.Boolean) on_thursdays = db.Column(db.Boolean) on_fridays = db.Column(db.Boolean) on_saturdays = db.Column(db.Boolean) on_sundays = db.Column(db.Boolean) until = db.Column(ArrowType) DAYS_OF_THE_WEEK = tuple(calendar.day_name[i][:3] for i in range(7)) def __contains__(self, user): """Check if user is in signups""" if not user.is_authenticated: return False return Signup.query.filter_by( user_id=user.id, event_id=self.id, is_active=True ).one_or_none() is not None @property def group(self): return Group.query.get(self.group_id) @property def signups(self): """Returns all participants""" return Signup.query.filter_by( event_id=self.id, is_active=True).all() @property def num_signups(self): """number of signups""" return int(Signup.query.filter_by( event_id=self.id, is_active=True).count()) @property def categories(self): """all event categories""" return [s.strip().split('(')[0] for s in g.event.setting('categories').value.split(',')] + ['Accepted', 'Waitlisted'] @property def category_defaults(self): """all event categories""" counts = [('Accepted', 0), ('Waitlisted', 0)] for s in g.event.setting('categories').value.split(','): data = s.strip().split('(') if len(data) > 1: counts.append((data[0], int(data[1][:-1]))) else: counts.append((data[0], 0)) return counts @property def days_of_the_week_booleans(self): """Returns boolean values for days of the week.""" return tuple(( self.on_mondays, self.on_tuesdays, self.on_wednesdays, self.on_thursdays, self.on_fridays, self.on_saturdays, self.on_sundays)) @property def days_of_the_week(self): """Returns list of days of the week.""" return tuple(day for day, value in zip( Event.DAYS_OF_THE_WEEK, self.days_of_the_week_booleans) if value) @property def category_counts(self): """all event categories""" counts = [] for s in g.event.setting('categories').value.split(',') + ('Accepted', 'Waitlisted'): category = s.strip().split('(')[0] counts.append((category, Signup.query.filter_by( category=category, is_active=True, event_id=self.id ))) return counts @property def num_non_waitlisted_signups(self): """Number of active signups""" return Event.query.join(Signup).filter( Event.id == self.id, Event.start >= arrow.now(), Signup.category != 'Waitlisted', Signup.is_active == True ).count() @classmethod def range(cls, event_start, event_end, shift_duration, shift_alignment): """Returns an iterable of time spans""" def start(i=None): """generates iterable of time spans in shift_duration from start""" i = i or event_start j = i.replace(minutes=shift_duration) while i < event_end: if i >= event_end: raise StopIteration if j > event_end: j = event_end yield i, j i = j j = j.replace(minutes=shift_duration) def hour(): """generates iterable of time spans per hour from start""" i, j = event_start, event_start.replace(minutes=shift_duration) if i.floor('hour') != i: j = event_start.replace(hours=1).floor('hour') yield i, j for i, j in start(j): yield i, j return locals()[shift_alignment.lower()]() @classmethod def split(cls, event_data, shift_duration, shift_alignment): """ Splits and saves event as a series of shifts :param event_data: dictionary of event attributes :param shift_duration: time in minutes :param shift_alignment: HOUR, START, END """ if shift_duration == 0: return Event(**event_data).save() return [Event(**event_data).update(start=i, end=j).save() for i, j in Event.range( event_data['start'], event_data['end'], shift_duration, shift_alignment)] @classmethod def from_parent(cls, parent, date=None): """ Create a new event from the parent object. All child events do NOT contain recurrence information. Only the parent event does. Args: parent: the parent event, containing recurrence information date: the date to create the new event on, transfer only the time of day from the old event Returns: new event object, on the new day but with old times """ start = parent.start end = parent.end if date: start = date.replace( hour=parent.start.hour, minute=parent.start.minute) end = start.replace( hour=parent.end.hour, minute=parent.end.minute) return parent.copy( parent_id=parent.id, start=start, end=end) @validates('frequency') def validate_frequency(self, _, address): """Check that frequency is an integer.""" return int(address[0]) def create_shift(self, yyyymmdd): """ Creates a child event. Note this child event does NOT contain recurrence information. Args: yyyymmdd: A date formatted as YYYYMMDD (e.g., 20160902) Returns: A child event """ return Event.from_parent(self, arrow.get(yyyymmdd, 'YYYYMMDD')) def copy(self, **kwargs): """Makes copy for newly-created shifts.""" data = dict( name=self.name, description=self.description, group_id=self.group_id) data.update(kwargs) return Event(**data) def get_shift_or_none(self, date): """Get shift for the provided date.""" current_day = date.replace(hour=0, minutes=0) next_day = current_day.replace(days=+1, seconds=-1) return Event.query.filter( Event.parent_id == self.id, Event.start >= current_day, Event.start <= next_day).one_or_none() def update(self, **kwargs): """Intercept updates to object.""" if 'days_of_the_week' in kwargs: dotw = kwargs.pop('days_of_the_week') self.set_byday(*[day in dotw for day in Event.DAYS_OF_THE_WEEK]) return super().update(**kwargs) def set_byday( self, on_mondays: bool=False, on_tuesdays: bool=False, on_wednesdays: bool=False, on_thursdays: bool=False, on_fridays: bool=False, on_saturdays: bool=False, on_sundays: bool=False) -> int: """Pass in booleans representing the days for which an event happens. Converts list of booleans into a bit representation. """ self.on_mondays = on_mondays self.on_tuesdays = on_tuesdays self.on_wednesdays = on_wednesdays self.on_thursdays = on_thursdays self.on_fridays = on_fridays self.on_saturdays = on_saturdays self.on_sundays = on_sundays def signups_by_category(self, **kwargs): """Returns all accepted participants""" if kwargs['category'] == '*': kwargs.pop('category') return Signup.query.filter_by( event_id=self.id, is_active=True, **kwargs).all() def split_existing(self, event_data, shift_duration, shift_alignment): """ Splits and saves existing event :param event_data: dictionary of event attributes :param shift_duration: time in minutes :param shift_alignment: HOUR, START, END """ if shift_duration == 0: return Event(**event_data).save() span_range = Event.range( event_data['start'], event_data['end'], shift_duration, shift_alignment) # modify existing event to match shift i, j = next(span_range) self.update(start=i, end=j).save() # create all other events event_data['parent_id'] = self.id event_data.pop('google_id', '') return [self] + [Event(**event_data).update(start=i, end=j).save() for i, j in span_range]
class EventRole(Role): """roles for event membership""" __tablename__ = 'event_role' event_id = db.Column(db.Integer, db.ForeignKey('event.id'))
class EventSetting(Setting): """settings for a PIAP event""" __tablename__ = 'event_setting' event_id = db.Column(db.Integer, db.ForeignKey('event.id'))
class GroupRole(Role): """roles for group membership""" __tablename__ = 'group_role' group_id = db.Column(db.Integer, db.ForeignKey('group.id'))
class GroupSetting(Setting): """settings for a PIAP group""" __tablename__ = 'group_setting' group_id = db.Column(db.Integer, db.ForeignKey('group.id'))
class UserSetting(Setting): """settings for a PIAP user""" __tablename__ = 'user_setting' user_id = db.Column(db.Integer, db.ForeignKey('user.id'))