class MITSPicture(MagModel): team_id = Column(UUID, ForeignKey('mits_team.id')) filename = Column(UnicodeText) content_type = Column(UnicodeText) extension = Column(UnicodeText) description = Column(UnicodeText) @property def url(self): return '../mits_applications/view_picture?id={}'.format(self.id) @property def filepath(self): return os.path.join(c.MITS_PICTURE_DIR, str(self.id))
class ArtShowPayment(MagModel): receipt_id = Column(UUID, ForeignKey('art_show_receipt.id', ondelete='SET NULL'), nullable=True) receipt = relationship('ArtShowReceipt', foreign_keys=receipt_id, cascade='save-update, merge', backref=backref('art_show_payments', cascade='save-update, merge')) amount = Column(Integer, default=0) type = Column(Choice(c.ART_SHOW_PAYMENT_OPTS), default=c.STRIPE, admin_only=True) when = Column(UTCDateTime, default=lambda: datetime.now(UTC))
class DeptMembershipRequest(MagModel): attendee_id = Column(UUID, ForeignKey('attendee.id')) # A NULL value for the department_id indicates the attendee is willing # to volunteer for any department (they checked "Anything" for # "Where do you want to help?"). department_id = Column(UUID, ForeignKey('department.id'), nullable=True) __mapper_args__ = {'confirm_deleted_rows': False} __table_args__ = ( UniqueConstraint('attendee_id', 'department_id'), Index('ix_dept_membership_request_attendee_id', 'attendee_id'), Index('ix_dept_membership_request_department_id', 'department_id'), )
class Shift(MagModel): job_id = Column(UUID, ForeignKey('job.id', ondelete='cascade')) attendee_id = Column(UUID, ForeignKey('attendee.id', ondelete='cascade')) worked = Column(Choice(c.WORKED_STATUS_OPTS), default=c.SHIFT_UNMARKED) rating = Column(Choice(c.RATING_OPTS), default=c.UNRATED) comment = Column(UnicodeText) __table_args__ = ( Index('ix_shift_job_id', job_id), Index('ix_shift_attendee_id', attendee_id), ) @property def name(self): return "{}'s {!r} shift".format(self.attendee.full_name, self.job.name)
class TabletopGame(MagModel): code = Column(UnicodeText) name = Column(UnicodeText) attendee_id = Column(UUID, ForeignKey('attendee.id')) returned = Column(Boolean, default=False) checkouts = relationship('TabletopCheckout', backref='game') _repr_attr_names = ['name'] @property def checked_out(self): try: return [c for c in self.checkouts if not c.returned][0] except Exception: pass
class AccessGroup(MagModel): """ Sets of accesses to grant to admin accounts. """ NONE = 0 LIMITED = 1 CONTACT = 2 DEPT = 3 FULL = 5 READ_LEVEL_OPTS = [(NONE, 'Same as Read-Write Access'), (LIMITED, 'Limited'), (CONTACT, 'Contact Info'), (DEPT, 'All Info in Own Dept(s)'), (FULL, 'All Info')] WRITE_LEVEL_OPTS = [(NONE, 'No Access'), (LIMITED, 'Limited'), (CONTACT, 'Contact Info'), (DEPT, 'All Info in Own Dept(s)'), (FULL, 'All Info')] name = Column(UnicodeText) access = Column(MutableDict.as_mutable(JSONB), default={}) read_only_access = Column(MutableDict.as_mutable(JSONB), default={}) @presave_adjustment def _disable_api_access(self): # orig_value_of doesn't seem to work for access and read_only_access so we always do this for account in self.admin_accounts: account.disable_api_access() def has_full_access(self, access_to, read_only=False): return self.has_access_level(access_to, self.FULL, read_only) def has_any_access(self, access_to, read_only=False): return self.has_access_level(access_to, self.LIMITED, read_only) def has_access_level(self, access_to, access_level, read_only=False, max_level=False): import operator if max_level: compare = operator.eq else: compare = operator.ge if read_only: return compare(int(self.access.get(access_to, 0)), access_level) \ or compare(int(self.read_only_access.get(access_to, 0)), access_level) return compare(int(self.access.get(access_to, 0)), access_level)
class StripeTransaction(MagModel): stripe_id = Column(UnicodeText, nullable=True) type = Column(Choice(c.TRANSACTION_TYPE_OPTS), default=c.PAYMENT) amount = Column(Integer) when = Column(UTCDateTime, default=lambda: datetime.now(UTC)) who = Column(UnicodeText) desc = Column(UnicodeText) fk_id = Column(UUID) fk_model = Column(UnicodeText)
class AttractionNotificationReply(MagModel): attraction_event_id = Column(UUID, ForeignKey('attraction_event.id'), nullable=True) attraction_id = Column(UUID, ForeignKey('attraction.id'), nullable=True) attendee_id = Column(UUID, ForeignKey('attendee.id'), nullable=True) notification_type = Column(Choice(Attendee._NOTIFICATION_PREF_OPTS)) from_phonenumber = Column(UnicodeText) to_phonenumber = Column(UnicodeText) sid = Column(UnicodeText, index=True) received_time = Column(UTCDateTime, default=lambda: datetime.now(pytz.UTC)) sent_time = Column(UTCDateTime, default=lambda: datetime.now(pytz.UTC)) body = Column(UnicodeText) @presave_adjustment def _fix_attraction_id(self): if not self.attraction_id and self.event: self.attraction_id = self.event.attraction_id
class IndieJudge(MagModel, ReviewMixin): admin_id = Column(UUID, ForeignKey('admin_account.id')) status = Column(Choice(c.MIVS_JUDGE_STATUS_OPTS), default=c.UNCONFIRMED) no_game_submission = Column(Boolean, nullable=True) genres = Column(MultiChoice(c.MIVS_INDIE_JUDGE_GENRE_OPTS)) platforms = Column(MultiChoice(c.MIVS_INDIE_PLATFORM_OPTS)) platforms_text = Column(UnicodeText) staff_notes = Column(UnicodeText) codes = relationship('IndieGameCode', backref='judge') reviews = relationship('IndieGameReview', backref='judge') email_model_name = 'judge' @property def judging_complete(self): return len(self.reviews) == len(self.game_reviews) @property def mivs_all_genres(self): return c.MIVS_ALL_GENRES in self.genres_ints @property def attendee(self): return self.admin_account.attendee @property def full_name(self): return self.attendee.full_name @property def email(self): return self.attendee.email
class WatchList(MagModel): first_names = Column(UnicodeText) last_name = Column(UnicodeText) email = Column(UnicodeText, default='') birthdate = Column(Date, nullable=True, default=None) reason = Column(UnicodeText) action = Column(UnicodeText) active = Column(Boolean, default=True) attendees = relationship('Attendee', backref=backref('watch_list', load_on_pending=True)) @property def full_name(self): return '{} {}'.format(self.first_names, self.last_name).strip() or 'Unknown' @property def first_name_list(self): return [name.strip().lower() for name in self.first_names.split(',')] @presave_adjustment def _fix_birthdate(self): if self.birthdate == '': self.birthdate = None
class Sale(MagModel): attendee_id = Column(UUID, ForeignKey('attendee.id', ondelete='set null'), nullable=True) what = Column(UnicodeText) cash = Column(Integer, default=0) mpoints = Column(Integer, default=0) when = Column(UTCDateTime, default=lambda: datetime.now(UTC)) reg_station = Column(Integer, nullable=True) payment_method = Column(Choice(c.SALE_OPTS), default=c.MERCH)
class ApiToken(MagModel): admin_account_id = Column(UUID, ForeignKey('admin_account.id')) token = Column(UUID, default=lambda: str(uuid.uuid4()), private=True) access = Column(MultiChoice(c.API_ACCESS_OPTS)) name = Column(UnicodeText) description = Column(UnicodeText) issued_time = Column(UTCDateTime, default=lambda: datetime.now(UTC)) revoked_time = Column(UTCDateTime, default=None, nullable=True)
class FoodRestrictions(MagModel): attendee_id = Column(UUID, ForeignKey('attendee.id'), unique=True) standard = Column(MultiChoice(c.FOOD_RESTRICTION_OPTS)) sandwich_pref = Column(MultiChoice(c.SANDWICH_OPTS)) freeform = Column(UnicodeText) def __getattr__(self, name): try: return super(FoodRestrictions, self).__getattr__(name) except AttributeError: restriction = getattr(c, name.upper()) if restriction not in c.FOOD_RESTRICTIONS: return MagModel.__getattr__(self, name) elif restriction == c.VEGAN and c.VEGAN in self.standard_ints: return False elif restriction == c.PORK and c.VEGAN in self.standard_ints: return True else: return restriction in self.standard_ints
class AdminAccount(MagModel): attendee_id = Column(UUID, ForeignKey('attendee.id'), unique=True) hashed = Column(UnicodeText) access = Column(MultiChoice(c.ACCESS_OPTS)) password_reset = relationship('PasswordReset', backref='admin_account', uselist=False) def __repr__(self): return '<{}>'.format(self.attendee.full_name) @staticmethod def is_nick(): return AdminAccount.admin_name() in c.JERKS @staticmethod def admin_name(): try: from uber.models import Session with Session() as session: return session.admin_attendee().full_name except: return None @staticmethod def admin_email(): try: from uber.models import Session with Session() as session: return session.admin_attendee().email except: return None @staticmethod def access_set(id=None): try: from uber.models import Session with Session() as session: id = id or cherrypy.session['account_id'] return set(session.admin_account(id).access_ints) except: return set()
class AttractionNotification(MagModel): attraction_event_id = Column(UUID, ForeignKey('attraction_event.id')) attraction_id = Column(UUID, ForeignKey('attraction.id')) attendee_id = Column(UUID, ForeignKey('attendee.id')) notification_type = Column(Choice(Attendee._NOTIFICATION_PREF_OPTS)) ident = Column(UnicodeText, index=True) sid = Column(UnicodeText) sent_time = Column(UTCDateTime, default=lambda: datetime.now(pytz.UTC)) subject = Column(UnicodeText) body = Column(UnicodeText) @presave_adjustment def _fix_attraction_id(self): if not self.attraction_id and self.event: self.attraction_id = self.event.attraction_id
class MITSApplicant(MagModel): team_id = Column(ForeignKey('mits_team.id')) attendee_id = Column(ForeignKey('attendee.id'), nullable=True) primary_contact = Column(Boolean, default=False) first_name = Column(UnicodeText) last_name = Column(UnicodeText) email = Column(UnicodeText) cellphone = Column(UnicodeText) contact_method = Column(Choice(c.MITS_CONTACT_OPTS), default=c.TEXTING) declined_hotel_space = Column(Boolean, default=False) requested_room_nights = Column(MultiChoice(c.MITS_ROOM_NIGHT_OPTS), default='') @property def full_name(self): return self.first_name + ' ' + self.last_name def has_requested(self, night): return night in self.requested_room_nights_ints
class IndieDeveloper(MagModel): studio_id = Column(UUID, ForeignKey('indie_studio.id')) # primary_contact == True just means they receive emails primary_contact = Column(Boolean, default=False) first_name = Column(UnicodeText) last_name = Column(UnicodeText) email = Column(UnicodeText) cellphone = Column(UnicodeText) @property def email_to_address(self): # Note: this doesn't actually do what we want right now # because the IndieDeveloper and attendee are not properly linked if self.matching_attendee: return self.matching_attendee.email return self.email @property def cellphone_num(self): if self.matching_attendee: return self.matching_attendee.cellphone return self.cellphone @property def full_name(self): return self.first_name + ' ' + self.last_name @property def matching_attendee(self): return self.session.query(Attendee).filter( func.lower(Attendee.first_name) == self.first_name.lower(), func.lower(Attendee.last_name) == self.last_name.lower(), func.lower(Attendee.email) == self.email.lower()).first()
class BaseEmailMixin(object): model = Column(UnicodeText) subject = Column(UnicodeText) body = Column(UnicodeText) sender = Column(UnicodeText) cc = Column(UnicodeText) bcc = Column(UnicodeText) _repr_attr_names = ['subject'] @property def body_with_body_tag_stripped(self): body = re.split(r'<\s*body[^>]*>', self.body)[-1] return re.split(r'<\s*\/\s*body\s*>', body)[0] @property def body_as_html(self): if self.is_html: return self.body_with_body_tag_stripped else: return normalize_newlines(self.body).replace('\n', '<br>') @property def is_html(self): return '<body' in self.body @property def model_class(self): if self.model and self.model != 'n/a': from uber.models import Session return Session.resolve_model(self.model) else: return None
class EventFeedback(MagModel): event_id = Column(UUID, ForeignKey('event.id')) attendee_id = Column(UUID, ForeignKey('attendee.id', ondelete='cascade')) headcount_starting = Column(Integer, default=0) headcount_during = Column(Integer, default=0) comments = Column(UnicodeText) rating = Column(Choice(c.PANEL_RATING_OPTS), default=c.UNRATED)
class TabletopEntrant(MagModel): tournament_id = Column(UUID, ForeignKey('tabletop_tournament.id')) attendee_id = Column(UUID, ForeignKey('attendee.id')) signed_up = Column(UTCDateTime, default=lambda: datetime.now(UTC)) confirmed = Column(Boolean, default=False) reminder = relationship('TabletopSmsReminder', backref='entrant', uselist=False) replies = relationship('TabletopSmsReply', backref='entrant') @presave_adjustment def _within_cutoff(self): if self.is_new: tournament = self.tournament or self.session.tabletop_tournament( self.tournament_id) cutoff = timedelta(minutes=c.TABLETOP_SMS_CUTOFF_MINUTES) if self.signed_up > tournament.event.start_time - cutoff: self.confirmed = True @property def should_send_reminder(self): stagger = timedelta(minutes=c.TABLETOP_SMS_STAGGER_MINUTES) reminder = timedelta(minutes=c.TABLETOP_SMS_REMINDER_MINUTES) return not self.confirmed \ and not self.reminder \ and localized_now() < self.tournament.event.start_time \ and localized_now() > self.signed_up + stagger \ and localized_now() > self.tournament.event.start_time - reminder def matches(self, message): sent = message.date_sent.replace(tzinfo=UTC) start_time_slack = timedelta(minutes=c.TABLETOP_TOURNAMENT_SLACK) return normalize_phone(self.attendee.cellphone) == message.from_ \ and self.reminder and sent > self.reminder.when \ and sent < self.tournament.event.start_time + start_time_slack __table_args__ = (UniqueConstraint('tournament_id', 'attendee_id', name='_tournament_entrant_uniq'), )
class DeptMembership(MagModel): is_dept_head = Column(Boolean, default=False) is_poc = Column(Boolean, default=False) is_checklist_admin = Column(Boolean, default=False) attendee_id = Column(UUID, ForeignKey('attendee.id')) department_id = Column(UUID, ForeignKey('department.id')) __mapper_args__ = {'confirm_deleted_rows': False} __table_args__ = ( UniqueConstraint('attendee_id', 'department_id'), Index('ix_dept_membership_attendee_id', 'attendee_id'), Index('ix_dept_membership_department_id', 'department_id'), ) @hybrid_property def has_role(self): return self.has_inherent_role or self.has_dept_role @has_role.expression def has_role(cls): return or_(cls.has_inherent_role, cls.has_dept_role) @hybrid_property def has_inherent_role(self): return self.is_dept_head or self.is_poc or self.is_checklist_admin @has_inherent_role.expression def has_inherent_role(cls): return or_(cls.is_dept_head == True, cls.is_poc == True, cls.is_checklist_admin == True) # noqa: E712 @hybrid_property def has_dept_role(self): return bool(self.dept_roles) @has_dept_role.expression def has_dept_role(cls): return exists().select_from(dept_membership_dept_role) \ .where(cls.id == dept_membership_dept_role.c.dept_membership_id)
class DeptRole(MagModel): name = Column(UnicodeText) description = Column(UnicodeText) department_id = Column(UUID, ForeignKey('department.id')) dept_memberships = relationship( 'DeptMembership', backref='dept_roles', cascade='save-update,merge,refresh-expire,expunge', secondary='dept_membership_dept_role') __table_args__ = ( UniqueConstraint('name', 'department_id'), Index('ix_dept_role_department_id', 'department_id'), ) @hybrid_property def dept_membership_count(self): return len(self.dept_memberships) @dept_membership_count.expression def dept_membership_count(cls): return func.count(cls.dept_memberships) @classproperty def _extra_apply_attrs(cls): return set(['dept_memberships_ids' ]).union(cls._extra_apply_attrs_restricted) @property def dept_memberships_ids(self): _, ids = self._get_relation_ids('dept_memberships') return [str(d.id) for d in self.dept_memberships] if ids is None else ids @dept_memberships_ids.setter def dept_memberships_ids(self, value): self._set_relation_ids('dept_memberships', DeptMembership, value)
class GuestPanel(MagModel): guest_id = Column(UUID, ForeignKey('guest_group.id'), unique=True) wants_panel = Column(Choice(c.GUEST_PANEL_OPTS), nullable=True) name = Column(UnicodeText) length = Column(UnicodeText) desc = Column(UnicodeText) tech_needs = Column(MultiChoice(c.TECH_NEED_OPTS)) other_tech_needs = Column(UnicodeText) @property def status(self): return self.wants_panel_label
class StripeTransaction(MagModel): stripe_id = Column(UnicodeText, nullable=True) type = Column(Choice(c.TRANSACTION_TYPE_OPTS), default=c.PENDING) amount = Column(Integer) when = Column(UTCDateTime, default=lambda: datetime.now(UTC)) who = Column(UnicodeText) desc = Column(UnicodeText) attendees = relationship('StripeTransactionAttendee', backref='stripe_transaction') groups = relationship('StripeTransactionGroup', backref='stripe_transaction')
class GuestInfo(MagModel): guest_id = Column(UUID, ForeignKey('guest_group.id'), unique=True) poc_phone = Column(UnicodeText) performer_count = Column(Integer, default=0) bringing_vehicle = Column(Boolean, default=False) vehicle_info = Column(UnicodeText) arrival_time = Column(UnicodeText) @property def status(self): return "Yes" if self.poc_phone else ""
class WatchList(MagModel): first_names = Column(UnicodeText) last_name = Column(UnicodeText) email = Column(UnicodeText, default='') birthdate = Column(Date, nullable=True, default=None) reason = Column(UnicodeText) action = Column(UnicodeText) active = Column(Boolean, default=True) attendees = relationship( 'Attendee', backref=backref('watch_list', load_on_pending=True)) @presave_adjustment def _fix_birthdate(self): if self.birthdate == '': self.birthdate = None
class GuestBio(MagModel): guest_id = Column(UUID, ForeignKey('guest_group.id'), unique=True) desc = Column(UnicodeText) website = Column(UnicodeText) facebook = Column(UnicodeText) twitter = Column(UnicodeText) other_social_media = Column(UnicodeText) teaser_song_url = Column(UnicodeText) pic_filename = Column(UnicodeText) pic_content_type = Column(UnicodeText) @property def pic_url(self): if self.uploaded_pic: return '{}/guests/view_bio_pic?id={}'.format(c.PATH, self.guest.id) return '' @property def pic_fpath(self): return os.path.join(c.GUESTS_BIO_PICS_DIR, self.id) @property def uploaded_pic(self): return os.path.exists(self.pic_fpath) @property def pic_extension(self): return filename_extension(self.pic_filename) @property def download_filename(self): name = self.guest.normalized_group_name return name + '_bio_pic.' + self.pic_extension @property def status(self): return 'Yes' if self.desc else ''
class IndieGameImage(MagModel): game_id = Column(UUID, ForeignKey('indie_game.id')) filename = Column(UnicodeText) content_type = Column(UnicodeText) extension = Column(UnicodeText) description = Column(UnicodeText) use_in_promo = Column(Boolean, default=False) is_screenshot = Column(Boolean, default=True) @property def url(self): return '{}/mivs_applications/view_image?id={}'.format(c.PATH, self.id) @property def filepath(self): return os.path.join(c.MIVS_GAME_IMAGE_DIR, str(self.id))
class Email(MagModel): fk_id = Column(UUID, nullable=True) ident = Column(UnicodeText) model = Column(UnicodeText) when = Column(UTCDateTime, default=lambda: datetime.now(UTC)) subject = Column(UnicodeText) dest = Column(UnicodeText) body = Column(UnicodeText) _repr_attr_names = ['subject'] @cached_property def fk(self): try: from uber.models import Session model_class = Session.resolve_model(self.model) query = self.session.query(model_class) return query.filter_by(id=self.fk_id).first() except: return None @property def rcpt_name(self): if self.fk: is_group = self.model == 'Group' return self.fk.leader.full_name if is_group else self.fk.full_name @property def rcpt_email(self): if self.fk: is_group = self.model == 'Group' return self.fk.leader.email if is_group else self.fk.email return self.dest or None @property def is_html(self): return '<body' in self.body @property def html(self): if self.is_html: body = re.split('<body[^>]*>', self.body)[1].split('</body>')[0] return safe_string(body) else: return safe_string(self.body.replace('\n', '<br/>'))
class Group: power = Column(Integer, default=0) power_fee = Column(Integer, default=0) power_usage = Column(UnicodeText) location = Column(UnicodeText, default='', admin_only=True) table_fee = Column(Integer, default=0) tax_number = Column(UnicodeText) @presave_adjustment def guest_groups_approved(self): if self.leader and self.leader.badge_type == c.GUEST_BADGE and self.status == c.UNAPPROVED: self.status = c.APPROVED @cost_property def power_cost(self): return self.power_fee if self.power_fee \ else c.POWER_PRICES[int(self.power)] @cost_property def table_cost(self): return self.table_fee if self.table_fee \ else c.TABLE_PRICES[int(self.tables)] @property def dealer_payment_due(self): if self.approved: return self.approved + timedelta(c.DEALER_PAYMENT_DAYS) @property def dealer_payment_is_late(self): if self.approved: return localized_now() > localize_datetime(self.dealer_payment_due) @presave_adjustment def dealers_add_badges(self): if self.is_dealer and self.is_new: self.can_add = True @property def tables_repr(self): return c.TABLE_OPTS[int(self.tables) - 1][1] if self.tables \ else "No Table" @property def dealer_max_badges(self): return c.MAX_DEALERS or min(math.ceil(self.tables) * 3, 12)