Пример #1
0
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))
Пример #2
0
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))
Пример #3
0
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'),
    )
Пример #4
0
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)
Пример #5
0
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
Пример #6
0
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)
Пример #7
0
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)
Пример #8
0
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
Пример #9
0
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
Пример #10
0
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
Пример #11
0
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)
Пример #12
0
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)
Пример #13
0
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
Пример #14
0
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()
Пример #15
0
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
Пример #16
0
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
Пример #17
0
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()
Пример #18
0
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
Пример #19
0
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)
Пример #20
0
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'), )
Пример #21
0
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)
Пример #22
0
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)
Пример #23
0
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
Пример #24
0
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')
Пример #25
0
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 ""
Пример #26
0
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
Пример #27
0
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 ''
Пример #28
0
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))
Пример #29
0
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/>'))
Пример #30
0
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)