class Fraternity(SurrogatePK, Model): """A fraternity.""" __tablename__ = 'fraternities' title = Column(db.String(80), nullable=False) capacity = Column(db.Integer(), nullable=False) users = relationship('User') parties = relationship('Party', cascade='delete', single_parent=True) can_have_parties = Column(db.Boolean(), default=True, nullable=False) school_id = reference_col('schools', nullable=False) school = relationship('School') def __repr__(self): """Represent instance as a unique string.""" return '<Fraternity({title})>'.format(title=self.title) @validates('title') def validate_title(self, key, title): """Validate the title of the fraternity.""" if not all([x.lower() in greek_letters for x in title.split()]): raise InvalidAPIUsage(payload={ 'error': locales.Error.INVALID_FRAT_NAME_TEMPLATE.format(title) }) return title
class Party(SurrogatePK, Model): """A party for a fraternity.""" __tablename__ = 'parties' name = Column(db.String(80), nullable=False) date = Column(db.Date(), nullable=False) started = Column(db.Boolean(), nullable=False, default=False) ended = Column(db.Boolean(), nullable=False, default=False) creator_id = reference_col('users', nullable=False) creator = relationship('User') fraternity_id = reference_col('fraternities', nullable=False) fraternity = relationship('Fraternity') guests = relationship('Guest', cascade='delete', single_parent=True) def __repr__(self): """Represent instance as a unique string.""" return '<Party({name})>'.format(name=self.name) @validates('fraternity', 'creator') def validate_fraternity(self, key, field): """Ensures that the creator is part of the fraternity""" if key is 'creator' and self.fraternity is not None: if field.fraternity != self.fraternity: raise InvalidAPIUsage( payload={'error': locales.Error.PARTY_CREATOR_MISALIGNED}, status_code=403) elif key is 'fraternity' and self.creator is not None: if self.creator.fraternity != field: raise InvalidAPIUsage( payload={'error': locales.Error.PARTY_CREATOR_MISALIGNED}, status_code=403) return field def is_on_guest_list(self, guest_name): """Validates whether or not the guest is on the party list.""" return guest_name.lower() in [ guest.name.lower() for guest in self.guests ] @property def male_guests(self): """Get the male guests""" return [guest for guest in self.guests if guest.is_male] @property def female_guests(self): """Get the female guests""" return [guest for guest in self.guests if not guest.is_male] def start(self): """Let's get it started""" self.started = True self.save() def end(self): """Ok, that's enough PARTY'S OVER.""" assert self.started, locales.Error.PARTY_END_BEFORE_START self.ended = True self.save()
class Guest(SurrogatePK, Model): """A guest for a party""" __tablename__ = 'guests' name = Column(db.String(80), nullable=False) host_id = reference_col('users', nullable=False) host = relationship('User') party_id = reference_col('parties', nullable=False) party = relationship('Party') is_at_party = Column(db.Boolean(), default=False, nullable=False) is_male = Column(db.Boolean(), nullable=False) entered_party_at = Column(db.DateTime) left_party_at = Column(db.DateTime) __table_args__ = (db.UniqueConstraint('name', 'party_id', name='_name_party_uc'), ) def __repr__(self): """Represent instance as a unique string.""" return '<Guest({name} at {party})>'.format(name=self.name, party=self.party.name) @validates('name') def validate_name(self, key, field): """Ensures that the guest is not already added to the party list""" if len(field) < 3: raise InvalidAPIUsage( payload={'error': locales.Error.GUEST_NAME_SHORT}, status_code=422) return field.lower() @property def json_dict(self): """Returns the guest as a JSON serializable python dict.""" return { 'name': titlecase(self.name), 'host': self.host.full_name, 'is_male': self.is_male, 'is_at_party': self.is_at_party, 'id': self.id, 'left_at': self.left_party_at, 'entered_at': self.entered_party_at } def leave_party(self): """The logic for a guest leaving the party.""" self.is_at_party = False if self.left_party_at is None: self.left_party_at = dt.utcnow() self.save() def enter_party(self): """The logic for a guest entering a party.""" self.is_at_party = True if self.entered_party_at is None: self.entered_party_at = dt.utcnow() self.save()
class Capacity(SurrogatePK, Model): """A capacity for a brother.""" __tablename__ = 'capacities' male_max = Column(db.Integer(), nullable=True) female_max = Column(db.Integer(), nullable=True) def __repr__(self): """Represent instance as a unique string.""" return '<Capacity(m[{}] | f[{}])>'.format(self.male_max, self.female_max)
class School(SurrogatePK, Model): """A school for an implementation of this software.""" __tablename__ = 'schools' title = Column(db.String(120), unique=True, nullable=False) short_title = Column(db.String(10)) fraternities = relationship('Fraternity', cascade='delete', single_parent=True) def __repr__(self): """Represent instance as a unique string.""" return '<School({title})>'.format(title=self.title) @property def abbreviation(self): return self.short_title @property def frats(self): return self.fraternities
class Role(SurrogatePK, Model): """A role for a user.""" __tablename__ = 'roles' title = Column(db.String(80), unique=True, nullable=False) users = relationship('User') def __init__(self, title, **kwargs): """Create instance.""" db.Model.__init__(self, title=title, **kwargs) def __repr__(self): """Represent instance as a unique string.""" return '<Role({title})>'.format(title=self.title) @validates('title') def validate_title(self, key, title): """Validate the title of the role.""" assert title in ['ifc_admin', 'chapter_admin', 'normal'], \ locales.Error.INVALID_ROLE return title
class Preuser(Model): """This is the model for users before they register. No validation because we're blindly trusting the data ingestion """ __tablename__ = 'preusers' id = db.Column(db.Integer, primary_key=True) email = Column(db.String(100)) first_name = Column(db.String(30)) last_name = Column(db.String(30)) chapter_admin = Column(db.Boolean()) ifc_admin = Column(db.Boolean()) school_title = Column(db.String(120)) fraternity_name = Column(db.String(80)) def __repr__(self): """Represent instance as a unique string.""" return '<Pre-registered user: ({last_name}, {first_name})>'.format( last_name=self.last_name, first_name=self.first_name )
class User(UserMixin, SurrogatePK, Model): """A user of the app.""" __tablename__ = 'users' email = Column(db.String(100), unique=True, nullable=False) #: The hashed password password = Column(db.String(128), nullable=True) first_name = Column(db.String(30), nullable=True) last_name = Column(db.String(30), nullable=True) active = Column(db.Boolean(), default=False) is_admin = Column(db.Boolean(), default=False) role_id = reference_col('roles', nullable=False) role = relationship('Role') fraternity_id = reference_col('fraternities', nullable=False) fraternity = relationship('Fraternity') capacity_id = reference_col('capacities', nullable=True) party_capacity = relationship('Capacity') parties = relationship('Party', cascade='delete', single_parent=True) def __init__(self, email, password=None, **kwargs): """Create instance.""" pre = Preuser.query.filter(Preuser.email == email).first() if pre is None: raise Forbidden() role = self.resolve_role_from_preuser(pre) if role is None and 'role_id' in kwargs: role = Role.find_by_id(kwargs['role_id']) fraternity = self.resolve_frat_from_preuser(pre) if fraternity is None and 'fraternity_id' in kwargs: fraternity = Fraternity.find_by_id(kwargs['fraternity_id']) # create the user db.Model.__init__(self, email=email, role=role, fraternity=fraternity, first_name=pre.first_name, last_name=pre.last_name) if password: self.set_password(password) else: self.password = None self.active = True def set_password(self, password): """Set password.""" self.password = bcrypt.generate_password_hash(password) def check_password(self, value): """Check password.""" return bcrypt.check_password_hash(self.password, value) def resolve_role_from_preuser(self, pre): """Figures out what the role is from the preregistered-user model.""" if pre.ifc_admin: return Role.query.filter(Role.title == 'ifc_admin').first() elif pre.chapter_admin: return Role.query.filter(Role.title == 'chapter_admin').first() else: return Role.query.filter(Role.title == 'normal').first() def resolve_frat_from_preuser(self, pre): """Finds the fraternity from the preregistered-user model.""" return Fraternity.query.filter_by(title=pre.fraternity_name)\ .join(Fraternity.school).filter_by(title=pre.school_title).first() @property def full_name(self): """Full user name.""" return '{0} {1}'.format(self.first_name, self.last_name) @property def username(self): """Username derived from email.""" return self.email.split('@')[0] @property def is_site_admin(self): """True if the user is a site admin.""" return self.role.title == 'ifc_admin' @property def is_chapter_admin(self): """True if the user is a chapter admin.""" return self.role.title == 'chapter_admin' @property def school_name(self): """Gets the shortened school name if there is one, otherwise returns the full name.""" if self.fraternity.school.short_title is not None: return self.fraternity.school.short_title else: return self.fraternity.school.title def can_delete_party_by_id(self, party_id): """True if the user can delete the given party.""" party = Party.find_or_404(party_id) flask.g.party = party return self.can_delete_party(party) def can_delete_party(self, party): """True if the user can delete the given party.""" return self.is_site_admin or (self.is_chapter_admin and self.fraternity == party.fraternity) @property def can_create_party(self): """True if the user can create a party.""" return self.is_site_admin or self.is_chapter_admin def can_view_party_by_id(self, party_id, guest_id=None): """True if the user can view the given party.""" party = Party.find_or_404(party_id) if guest_id is not None: guest = Guest.find_or_404(guest_id) flask.g.guest = guest flask.g.party = party return self.can_view_party(party) def can_manage_brother_by_id(self, brother_id): """True if the user can manage the brother.""" brother = User.find_or_404(brother_id) flask.g.brother = brother return self def can_manage_brother(self, brother): """True if the user can manage the brother.""" return self.fraternity_id == brother.fraternity_id and \ (self.is_admin or self.is_chapter_admin or self.is_site_admin) @property def can_manage_fraternity(self): """True if the user is an admin of any kind.""" return self.is_admin or self.is_site_admin or self.is_chapter_admin def can_view_party(self, party): """True if the user can view the guest list of a party""" return self.is_site_admin or party.fraternity == self.fraternity def can_edit_guest_by_id(self, guest_id, party_id=None): """True if the user can edit the guest.""" guest = Guest.find_or_404(guest_id) if party_id is not None: party = Party.find_or_404(party_id) flask.g.party = party flask.g.guest = guest return self.can_edit_guest(guest) def can_edit_guest(self, guest): """True if the user can edit the guest.""" return guest.host == self def __repr__(self): """Represent instance as a unique string.""" return '<User({email})>'.format(email=self.email) @property def json_dict(self): """Represent the instance as a dict that can be converted to JSON.""" return { 'username': self.username, 'first_name': self.first_name, 'last_name': self.last_name, 'full_name': self.full_name, 'id': self.id }