示例#1
0
class Postage(DB.Model):
    """Model for representing postage for one or more tickets."""
    __tablename__ = 'postage'
    object_id = DB.Column(DB.Integer, primary_key=True)

    paid = DB.Column(DB.Boolean(), default=False, nullable=False)
    postage_type = DB.Column(DB.Unicode(50), nullable=False)
    price = DB.Column(DB.Integer(), nullable=False)
    address = DB.Column(DB.Unicode(200), nullable=True)
    cancelled = DB.Column(DB.Boolean(), default=False, nullable=False)
    posted = DB.Column(DB.Boolean(), default=False, nullable=False)

    tickets = DB.relationship('Ticket',
                              secondary=POSTAGE_TICKET_LINK,
                              backref=DB.backref('postage',
                                                 lazy=False,
                                                 uselist=False),
                              lazy='dynamic')

    owner_id = DB.Column(DB.Integer,
                         DB.ForeignKey('user.object_id'),
                         nullable=True)
    owner = DB.relationship('User',
                            backref=DB.backref('postage_entries',
                                               lazy='dynamic'))

    def __init__(self, owner, postage_option, tickets, address=None):
        self.owner = owner
        self.postage_type = postage_option.name
        self.price = postage_option.price
        self.address = address
        self.tickets = tickets

        if self.price == 0:
            self.paid = True
示例#2
0
class AdminFee(DB.Model):
    """Model for representing an administration fee."""
    __tablename__ = 'admin_fee'

    object_id = DB.Column(DB.Integer, primary_key=True)

    amount = DB.Column(DB.Integer(), nullable=False)
    reason = DB.Column(DB.UnicodeText(), nullable=False)
    paid = DB.Column(DB.Boolean(), nullable=False, default=False)

    charged_to_id = DB.Column(DB.Integer,
                              DB.ForeignKey('user.object_id'),
                              nullable=False)
    charged_to = DB.relationship('User',
                                 backref=DB.backref('admin_fees_charged',
                                                    lazy='dynamic'),
                                 foreign_keys=[charged_to_id])

    charged_by_id = DB.Column(DB.Integer,
                              DB.ForeignKey('user.object_id'),
                              nullable=False)
    charged_by = DB.relationship('User',
                                 backref=DB.backref('admin_fees_created',
                                                    lazy='dynamic'),
                                 foreign_keys=[charged_by_id])

    def __init__(self, amount, reason, charged_to, charged_by):
        self.amount = amount
        self.reason = reason
        self.charged_to = charged_to
        self.charged_by = charged_by

    def __repr__(self):
        return '<AdminFee {0}: £{1}>'.format(self.object_id,
                                             self.amount_pounds)

    @property
    def amount_pounds(self):
        """Get the fee amount as a string of pounds and pence."""
        amount = '{0:03d}'.format(self.amount)
        return amount[:-2] + '.' + amount[-2:]

    def mark_as_paid(self):
        """Email the creator of this fee when it has been paid."""
        self.paid = True

        if "Ticket Upgrade:" in self.reason:
            possible_tickets = [int(x) for x in self.reason[16:].split(',')]
            for ticket in self.charged_to.active_tickets:
                if ticket.object_id in possible_tickets:
                    ticket.add_note('Upgrade')
        else:
            APP.email_manager.send_template(self.charged_by.email,
                                            'Administration fee paid.',
                                            'admin_fee_paid.email',
                                            fee=self)
示例#3
0
class DietaryRequirements(DB.Model):
    """Model for representing a users affiliation to their college."""
    __tablename__ = 'dietary_requirements'
    object_id = DB.Column(DB.Integer, primary_key=True)

    user_id = DB.Column(DB.Integer,
                        DB.ForeignKey('user.object_id'),
                        nullable=False)
    user = DB.relationship('User',
                           backref=DB.backref('dietary_requirements',
                                              uselist=False))

    pescetarian = DB.Column(DB.Boolean, default=False, nullable=False)
    vegetarian = DB.Column(DB.Boolean, default=False, nullable=False)
    vegan = DB.Column(DB.Boolean, default=False, nullable=False)
    gluten_free = DB.Column(DB.Boolean, default=False, nullable=False)
    nut_free = DB.Column(DB.Boolean, default=False, nullable=False)
    dairy_free = DB.Column(DB.Boolean, default=False, nullable=False)
    egg_free = DB.Column(DB.Boolean, default=False, nullable=False)
    seafood_free = DB.Column(DB.Boolean, default=False, nullable=False)

    other = DB.Column(DB.String(200), nullable=True)

    def __init__(self, user):
        self.user = user
示例#4
0
class Waiting(DB.Model):
    """Model for entries on the waiting list."""
    __tablename__ = 'waiting'
    object_id = DB.Column(DB.Integer, primary_key=True)

    waiting_since = DB.Column(DB.DateTime(), nullable=False)
    waiting_for = DB.Column(DB.Integer(), nullable=False)

    user_id = DB.Column(DB.Integer,
                        DB.ForeignKey('user.object_id'),
                        nullable=False)
    user = DB.relationship('User',
                           backref=DB.backref('waiting', lazy='dynamic'),
                           foreign_keys=[user_id])

    def __init__(self, user, waiting_for):
        self.user = user
        self.waiting_for = waiting_for

        self.waiting_since = datetime.datetime.utcnow()

    def __repr__(self):
        return '<Waiting: {0} for {1} ticket{2}>'.format(
            self.user.full_name, self.waiting_for,
            '' if self.waiting_for == 1 else 's')
示例#5
0
class CardTransaction(transaction.Transaction):
    """Model for representing a card transaction."""
    __tablename__ = 'card_transaction'
    __mapper_args__ = {'polymorphic_identity': 'Card'}
    object_id = DB.Column(DB.Integer, primary_key=True)

    object_id = DB.Column(DB.Integer(),
                          DB.ForeignKey('transaction.object_id'),
                          primary_key=True)

    eway_transaction_id = DB.Column(
        DB.Integer(),
        DB.ForeignKey('eway_transaction.object_id'),
        nullable=True)
    eway_transaction = DB.relationship('EwayTransaction',
                                       backref=DB.backref('transactions',
                                                          lazy='dynamic'))

    def __init__(self, user, eway_trans=None):
        super(CardTransaction, self).__init__(user, 'Card')

        if eway_transaction is not None:
            self.eway_transaction = eway_trans

    def __repr__(self):
        return '<CardTransaction({0}): {1} item(s)>'.format(
            self.object_id, self.items.count())
示例#6
0
class RoundupDonation(DB.Model):
    """Model for representing a roundup donation"""
    __tablename__ = 'roundup_donation'
    object_id = DB.Column(DB.Integer, primary_key=True)

    base_donation_amt = DB.Column(
        DB.Integer(),
        nullable = False
    )

    total_amount = DB.Column(
        DB.Integer(),
        nullable = False
    )

    charged_to_id = DB.Column(
        DB.Integer,
        DB.ForeignKey('user.object_id'),
        nullable = False
    )

    charged_to = DB.relationship(
        'User',
        backref = DB.backref(
            'roundup_donation',
            lazy = 'dynamic'
        ),
        foreign_keys=[charged_to_id]
    )

    def __init__(self, amount, charged_to):
        # Internal representaion of amt is integer. So need to multiply by
        # 100
        self.base_donation_amt = amount
        self.charged_to = charged_to
        self.total_amount = 0

    def __repr__(self):
        return '<RoundupDonation {0}:'
        return '<RoundupDonation {0}: £{1}>'.format(self.object_id, self.amount_pounds)

    @property
    def amount_pounds(self):
        """Get the fee amount as a string of pounds and pence."""
        amount = '{0:03d}'.format(self.total_amount)
        return amount[:-2] + '.' + amount[-2:]

    def apply(self, tickets):
        """Apply the roundup donation to a set of tickets
        """
        return [self.apply_to_ticket(t) for t in tickets]

    def apply_to_ticket(self, ticket):
        # TODO: Make sure that this is correct (it looks like the prices
        # are being represented by an int that is multiplied by 10???)
        ticket.price = ticket.price + self.base_donation_amt
        self.total_amount = self.total_amount + self.base_donation_amt
        ticket.add_note('Roundup donation amt {0}'.format(self.base_donation_amt))
        return ticket
示例#7
0
class TicketTransactionItem(transaction_item.TransactionItem):
    """Model for representing a ticket in a transaction."""
    __tablename__ = 'ticket_transaction_item'
    __mapper_args__ = {'polymorphic_identity': 'Ticket'}
    object_id = DB.Column(DB.Integer, primary_key=True)

    object_id = DB.Column(DB.Integer(),
                          DB.ForeignKey('transaction_item.object_id'),
                          primary_key=True)

    is_refund = DB.Column(DB.Boolean, nullable=False, default=False)

    ticket_id = DB.Column(DB.Integer,
                          DB.ForeignKey('ticket.object_id'),
                          nullable=False)
    ticket = DB.relationship('Ticket',
                             backref=DB.backref('transaction_items',
                                                lazy='dynamic'))

    def __init__(self, transaction, ticket, is_refund=False):
        super(TicketTransactionItem, self).__init__(transaction, 'Ticket')

        self.ticket = ticket
        self.is_refund = is_refund

    @property
    def value(self):
        """Get the value of this transaction item."""
        if self.is_refund:
            return 0 - self.ticket.price
        else:
            return self.ticket.price

    @property
    def description(self):
        """Get a description of the transaction item."""
        return '{0}{1} Ticket ({2:05d})'.format(
            'Refund of ' if self.is_refund else '', self.ticket_type,
            self.ticket_id)
示例#8
0
class PostageTransactionItem(transaction_item.TransactionItem):
    """Model for representing postage in a transaction."""
    __tablename__ = 'postage_transaction_item'
    __mapper_args__ = {'polymorphic_identity': 'Postage'}
    object_id = DB.Column(DB.Integer, primary_key=True)

    object_id = DB.Column(DB.Integer(),
                          DB.ForeignKey('transaction_item.object_id'),
                          primary_key=True)

    is_refund = DB.Column(DB.Boolean, nullable=False, default=False)

    postage_id = DB.Column(DB.Integer,
                           DB.ForeignKey('postage.object_id'),
                           nullable=False)
    postage = DB.relationship('Postage',
                              backref=DB.backref('transaction_items',
                                                 lazy='dynamic'))

    def __init__(self, transaction, postage, is_refund=False):
        super(PostageTransactionItem, self).__init__(transaction, 'Postage')

        self.postage = postage
        self.is_refund = is_refund

    @property
    def value(self):
        """Get the value of this transaction item."""
        if self.is_refund:
            return 0 - self.postage.price
        else:
            return self.postage.price

    @property
    def description(self):
        """Get a description of transaction item."""
        return '{0}{1} Postage'.format('Refund of ' if self.is_refund else '',
                                       self.postage.postage_type)
class TransactionItem(DB.Model):
    """Model for representing an item in a transaction.

    Not used directly, use GenericTransactionItem, TicketTransactionItem,
    PostageTransactionItem, AdminFeeTransactionItem subtypes instead.
    """
    __tablename__ = 'transaction_item'
    object_id = DB.Column(DB.Integer, primary_key=True)

    item_type = DB.Column(
        DB.Enum(
            'Ticket',
            'Generic',
            'Postage',
            'AdminFee',
        ),
        nullable=False
    )

    transaction_id = DB.Column(
        DB.Integer,
        DB.ForeignKey('transaction.object_id'),
        nullable=True
    )
    transaction = DB.relationship(
        'Transaction',
        backref=DB.backref(
            'items',
            lazy='dynamic'
        )
    )

    __mapper_args__ = {'polymorphic_on': item_type}

    def __init__(self, transaction, item_type):
        self.transaction = transaction
        self.item_type = item_type
示例#10
0
class AdminFeeTransactionItem(transaction_item.TransactionItem):
    """Model for representing an admin fee in a transaction."""
    __tablename__ = 'admin_fee_transaction_item'
    __mapper_args__ = {'polymorphic_identity': 'AdminFee'}
    # id = DB.Column(DB.Integer, primary_key=True)

    object_id = DB.Column(DB.Integer(),
                          DB.ForeignKey('transaction_item.object_id'),
                          primary_key=True)

    is_refund = DB.Column(DB.Boolean, nullable=False, default=False)

    admin_fee_id = DB.Column(DB.Integer,
                             DB.ForeignKey('admin_fee.object_id'),
                             nullable=False)
    admin_fee = DB.relationship('AdminFee',
                                backref=DB.backref('transaction_items',
                                                   lazy='dynamic'))

    def __init__(self, transaction, admin_fee, is_refund=False):
        super(AdminFeeTransactionItem, self).__init__(transaction, 'AdminFee')

        self.admin_fee = admin_fee
        self.is_refund = is_refund

    @property
    def value(self):
        """Get the value of this transaction item."""
        if self.is_refund:
            return 0 - self.admin_fee.amount
        else:
            return self.admin_fee.amount

    @property
    def description(self):
        """Get a description of the item."""
        return '{0} Admin Fee'.format('Refund of ' if self.is_refund else '')
示例#11
0
class PurchaseGroup(DB.Model):
    """Model for a purchase group to allow pooling allowances."""
    __tablename__ = 'purchase_group'
    object_id = DB.Column(DB.Integer, primary_key=True)

    code = DB.Column(DB.Unicode(10), unique=True, nullable=False)

    leader_id = DB.Column(DB.Integer,
                          DB.ForeignKey('user.object_id'),
                          nullable=False)
    leader = DB.relationship('User', foreign_keys=[leader_id])

    members = DB.relationship('User',
                              secondary=GROUP_MEMBER_LINK,
                              backref=DB.backref('purchase_group',
                                                 lazy=False,
                                                 uselist=False),
                              lazy='dynamic')

    disbanded = DB.Column(DB.Boolean(), default=False, nullable=False)
    purchased = DB.Column(DB.Boolean(), default=False, nullable=False)

    def __init__(self, leader):
        self.leader = leader
        self.members = [leader]

        self.code = util.generate_key(10)

    def __repr__(self):
        return '<PurchaseGroup({0}): {1} tickets, £{2}>'.format(
            self.object_id, self.total_requested, self.total_value_pounds)

    @property
    def total_value(self):
        """Get the total value of tickets requested by this group in pence."""
        return sum(request.value for request in self.requests)

    @property
    def total_value_pounds(self):
        """Get the total value of this group in pounds and pence."""
        value = '{0:03d}'.format(self.total_value)

        return value[:-2] + '.' + value[-2:]

    @property
    def total_requested(self):
        """Get the total number of tickets requested by this group."""
        return sum(request.number_requested for request in self.requests)

    @property
    def total_guest_tickets_requested(self):
        """Get the total number of guest tickets requested by this group."""
        return sum(request.number_requested for request in self.requests
                   if request.ticket_type.counts_towards_guest_limit)

    @staticmethod
    def get_by_code(code):
        """Get a purchase group object by its code."""
        group = PurchaseGroup.query.filter(PurchaseGroup.code == code).first()

        if not group:
            return None

        return group
示例#12
0
class User(DB.Model):
    """Model for users."""
    __tablename__ = 'user'

    object_id = DB.Column(DB.Integer, primary_key=True)

    # Class level properties for Flask-Login
    #
    # Sessions don't expire, and no users are anonymous, so these can be hard
    # coded
    is_authenticated = True
    is_anonymous = False

    email = DB.Column(DB.Unicode(120), unique=True, nullable=False)
    new_email = DB.Column(DB.Unicode(120), unique=True, nullable=True)
    password_hash = DB.Column(DB.BINARY(60), nullable=False)
    forenames = DB.Column(DB.Unicode(120), nullable=False)
    surname = DB.Column(DB.Unicode(120), nullable=False)
    full_name = DB.column_property(forenames + ' ' + surname)
    phone = DB.Column(DB.Unicode(20), nullable=False)
    phone_verification_code = DB.Column(DB.Unicode(6), nullable=True)
    phone_verified = DB.Column(DB.Boolean, nullable=False, default=False)
    secret_key = DB.Column(DB.Unicode(64), nullable=True)
    secret_key_expiry = DB.Column(DB.DateTime(), nullable=True)
    verified = DB.Column(DB.Boolean, default=False, nullable=False)
    deleted = DB.Column(DB.Boolean, default=False, nullable=False)
    note = DB.Column(DB.UnicodeText, nullable=True)
    role = DB.Column(DB.Enum('User', 'Admin'), nullable=False)

    college_id = DB.Column(DB.Integer,
                           DB.ForeignKey('college.object_id'),
                           nullable=False)
    college = DB.relationship('College',
                              backref=DB.backref('users', lazy='dynamic'))

    affiliation_id = DB.Column(DB.Integer,
                               DB.ForeignKey('affiliation.object_id'),
                               nullable=False)
    affiliation = DB.relationship('Affiliation',
                                  backref=DB.backref('users', lazy='dynamic'))

    battels_id = DB.Column(DB.Integer,
                           DB.ForeignKey('battels.object_id'),
                           nullable=True)
    battels = DB.relationship('Battels',
                              backref=DB.backref('user', uselist=False))

    affiliation_verified = DB.Column(DB.Boolean, default=False, nullable=True)

    photo_id = DB.Column(DB.Integer,
                         DB.ForeignKey('photo.object_id'),
                         nullable=True)
    photo = DB.relationship('Photo', backref=DB.backref('user', uselist=False))

    def has_tickets(self):
        if ticket.Ticket.query.filter_by(owner_id=self.object_id).count() > 0:
            return True
        return False

    def has_unpaid_tickets(self):
        if ticket.Ticket.query.filter_by(owner_id=self.object_id,
                                         paid=0).count() > 0:
            return True
        return False

    def has_paid_tickets(self):
        if ticket.Ticket.query.filter_by(owner_id=self.object_id,
                                         paid=1).count() > 0:
            return True
        return False

    def can_claim_ticket(self):
        return True

#todo: check logic

    def has_collected_tickets(self):
        return False

    def has_uncollected_tickets(self):
        return False

    def has_held_ticket(self):
        # if ticket.Ticket.query.filter_by(holder_id=self.object_id):
        #     return True
        return False

    def can_update_details(self):
        return APP.config['ENABLE_CHANGING_DETAILS']

    def __init__(self, email, password, forenames, surname, phone, college,
                 affiliation, photo):
        self.email = email
        self.forenames = forenames
        self.surname = surname
        self.phone = phone
        self.college = college
        self.affiliation = affiliation
        self.photo = photo

        self.set_password(password)

        self.secret_key = util.generate_key(64)
        self.verified = False
        self.deleted = False
        self.role = 'User'
        if affiliation.name == 'None':
            self.affiliation_verified = True
        else:
            self.affiliation_verified = False
            #todo add logic for checking if they are on the member list

        self.battels = battels.Battels.query.filter(
            battels.Battels.email == email).first()

    def __repr__(self):
        return '<User {0}: {1} {2}>'.format(self.object_id, self.forenames,
                                            self.surname)

    def can_pay_by_battels(self):
        return False

    @property
    def group_purchase_requests(self):
        """Get this user's group purchase requests.

        Not just a database backref so that old requests can hang around when
        the user leaves a group.
        """
        if self.purchase_group:
            for request in self.purchase_group.requests:
                if request.requester == self:
                    yield request

    def group_purchase_requested(self, ticket_type_slug):
        """Get how many of a given ticket type the user has requested."""
        for request in self.group_purchase_requests:
            if request.ticket_type_slug == ticket_type_slug:
                return request.number_requested

        return 0

    @property
    def total_group_purchase_requested(self):
        """Get the total number of tickets requested by this user."""
        return sum(request.number_requested
                   for request in self.group_purchase_requests)

    @property
    def total_group_purchase_value(self):
        """Get the total number of tickets requested by this user."""
        value = '{0:03d}'.format(
            sum(request.value for request in self.group_purchase_requests))

        return value[:-2] + '.' + value[-2:]

    def check_password(self, candidate):
        """Check if a password matches the hash stored for the user.

        Runs the bcrypt.Bcrypt checking routine to validate the password.

        Args:
            candidate: (str) the candidate password

        Returns:
            (bool) whether the candidate password matches the stored hash
        """
        return BCRYPT.check_password_hash(self.password_hash, candidate)

    def set_password(self, password):
        """Set the password for the user.

        Hashes the password using bcrypt and stores the resulting hash.

        Args:
            password: (str) new password for the user.
        """
        self.password_hash = BCRYPT.generate_password_hash(password)

    def promote(self):
        """Make the user an admin."""
        self.role = 'Admin'

    def demote(self):
        """Make the user an ordinary user (no admin privileges)"""
        self.role = 'User'

    @property
    def is_admin(self):
        """Check if the user is an admin, or is currently being impersonated.

        For future-proofing purposes, the role of the impersonating user is also
        checked.
        """
        return self.role == 'Admin' or (
            'actor_id' in flask.session
            and User.get_by_id(flask.session['actor_id']).role == 'Admin')

    @property
    def is_waiting(self):
        """Is the user on the waiting list?"""
        return self.waiting.count() > 0

    @property
    def active_tickets(self):
        """Get the active tickets owned by the user."""
        return self.tickets.filter(ticket.Ticket.cancelled == False  # pylint: disable=singleton-comparison
                                   )

    @property
    def active_ticket_count(self):
        """How many active tickets does the user own?"""
        return self.active_tickets.count()

    @property
    def waiting_for(self):
        """How many tickets is the user waiting for?"""
        return sum([x.waiting_for for x in self.waiting])

    @property
    def is_verified(self):
        """Has the user's email address been verified?"""
        return self.verified

    @property
    def is_deleted(self):
        """Has the user been deleted?

        In order to maintain referential integrity, when a user is deleted we
        scrub their personal details, but maintain the user object referenced by
        log entries, tickets, transactions etc.
        """
        return self.deleted

    @property
    def is_active(self):
        """Is the user active?

        This method is specifically for the use of the Flask-Login extension,
        and refers to whether the user can log in.
        """
        return self.is_verified and not self.is_deleted

    def get_id(self):
        """What is this user's ID?

        This method is specifically for the use of the Flask-Login extension,
        and is a defined class method which returns a unique identifier for the
        user, in this case their database ID.
        """
        return unicode(self.object_id)

    @staticmethod
    def get_by_email(email):
        """Get a user object by the user's email address."""
        user = User.query.filter(User.email == email).first()

        if not user:
            return None

        return user

    def add_manual_battels(self):
        """Manually add a battels account for the user

        If we don't have a battels account automatically matched to the user,
        the admin can manually create one for them.
        """
        self.battels = battels.Battels.query.filter(
            battels.Battels.email == self.email).first()

        if not self.battels:
            self.battels = battels.Battels(None, self.email, None,
                                           self.surname, self.forenames, True)
            DB.session.add(self.battels)

        DB.session.commit()

    @staticmethod
    def write_csv_header(csv_writer):
        """Write the header of a CSV export file."""
        csv_writer.writerow([
            'User ID',
            'Email',
            'Forenames',
            'Surname',
            'Phone Number',
            'Notes',
            'Role',
            'College',
            'Affiliation',
            'Battels ID',
        ])

    def write_csv_row(self, csv_writer):
        """Write this object as a row in a CSV export file."""
        csv_writer.writerow([
            self.object_id,
            self.email,
            self.forenames,
            self.surname,
            self.phone,
            self.note,
            self.role,
            self.college.name,
            self.affiliation.name,
            self.battels.battels_id if self.battels is not None else 'N/A',
        ])
示例#13
0
class Log(DB.Model):
    """Model for log entries persisted to the database."""
    __tablename__ = 'log'
    object_id = DB.Column(DB.Integer, primary_key=True)

    timestamp = DB.Column(DB.DateTime, nullable=False)
    ip_address = DB.Column(DB.Unicode(45), nullable=False)
    action = DB.Column(DB.UnicodeText())

    actor_id = DB.Column(DB.Integer(),
                         DB.ForeignKey('user.object_id'),
                         nullable=True)
    actor = DB.relationship('User',
                            backref=DB.backref('actions', lazy='dynamic'),
                            foreign_keys=[actor_id])

    user_id = DB.Column(DB.Integer(),
                        DB.ForeignKey('user.object_id'),
                        nullable=True)
    user = DB.relationship('User',
                           backref=DB.backref('events', lazy='dynamic'),
                           foreign_keys=[user_id])

    tickets = DB.relationship('Ticket',
                              secondary=LOG_TICKET_LINK,
                              backref=DB.backref('events', lazy='dynamic'),
                              lazy='dynamic')

    transaction_id = DB.Column(DB.Integer(),
                               DB.ForeignKey('transaction.object_id'),
                               nullable=True)
    transaction = DB.relationship('Transaction',
                                  backref=DB.backref('events', lazy='dynamic'))

    purchase_group_id = DB.Column(DB.Integer(),
                                  DB.ForeignKey('purchase_group.object_id'),
                                  nullable=True)
    purchase_group = DB.relationship('PurchaseGroup',
                                     backref=DB.backref('events',
                                                        lazy='dynamic'))

    admin_fee_id = DB.Column(DB.Integer(),
                             DB.ForeignKey('admin_fee.object_id'),
                             nullable=True)
    admin_fee = DB.relationship('AdminFee',
                                backref=DB.backref('events', lazy='dynamic'))

    def __init__(self,
                 ip_address,
                 action,
                 actor,
                 user,
                 tickets=None,
                 transaction=None,
                 purchase_group=None,
                 admin_fee=None):
        if tickets is None:
            tickets = []

        self.timestamp = datetime.datetime.utcnow()
        self.ip_address = ip_address
        self.action = action
        self.actor = actor
        self.user = user
        self.tickets = tickets
        self.transaction = transaction
        self.purchase_group = purchase_group
        self.admin_fee = admin_fee

    def __repr__(self):
        return '<Log {0}: {1}>'.format(
            self.object_id, self.timestamp.strftime('%Y-%m-%d %H:%m (UTC)'))

    @staticmethod
    def write_csv_header(csv_writer):
        """Write the header of a CSV export file."""
        csv_writer.writerow([
            'Log Entry ID',
            'Timestamp',
            'IP Address',
            'Action',
            'Actor\'s User ID',
            'Actor\'s Name',
            'Target\'s User ID',
            'Target\'s Name',
            'Relevant Ticket IDs',
            'Relevant Transaction ID',
            'Relevant Purchase Group ID',
            'Relevant Admin Fee ID',
        ])

    def write_csv_row(self, csv_writer):
        """Write this object as a row in a CSV export file."""
        csv_writer.writerow([
            self.object_id,
            self.timestamp.strftime('%Y-%m-%d %H:%M:%S'),
            self.ip_address,
            self.action,
            self.actor_id if self.actor_id is not None else 'N/A',
            self.actor if self.actor is not None else 'N/A',
            self.user_id if self.user_id is not None else 'N/A',
            self.user if self.user is not None else 'N/A',
            ','.join(str(ticket.object_id) for ticket in self.tickets),
            (self.transaction_id
             if self.transaction_id is not None else 'N/A'),
            (self.purchase_group_id
             if self.purchase_group_id is not None else 'N/A'),
            (self.admin_fee_id if self.admin_fee_id is not None else 'N/A'),
        ])
示例#14
0
class Announcement(DB.Model):
    """Model for an announcement sent to registered users."""
    __tablename__ = 'announcement'
    object_id = DB.Column(DB.Integer, primary_key=True)

    timestamp = DB.Column(DB.DateTime(), nullable=False)
    content = DB.Column(DB.UnicodeText(65536), nullable=False)
    subject = DB.Column(DB.UnicodeText(256), nullable=False)
    send_email = DB.Column(DB.Boolean, default=True, nullable=False)
    use_noreply = DB.Column(DB.Boolean, default=False, nullable=False)
    email_sent = DB.Column(DB.Boolean, default=False, nullable=False)

    sender_id = DB.Column(DB.Integer,
                          DB.ForeignKey('user.object_id'),
                          nullable=False)
    sender = DB.relationship('User',
                             backref=DB.backref('announcements-sent',
                                                lazy='dynamic'))

    college_id = DB.Column(DB.Integer,
                           DB.ForeignKey('college.object_id'),
                           nullable=True)
    college = DB.relationship('College',
                              backref=DB.backref('announcements',
                                                 lazy='dynamic'))

    affiliation_id = DB.Column(DB.Integer,
                               DB.ForeignKey('affiliation.object_id'),
                               nullable=True)
    affiliation = DB.relationship('Affiliation',
                                  backref=DB.backref('announcements-received',
                                                     lazy='dynamic'))

    is_waiting = DB.Column(DB.Boolean, nullable=True)
    has_tickets = DB.Column(DB.Boolean, nullable=True)
    holds_ticket = DB.Column(DB.Boolean, nullable=True)
    has_collected = DB.Column(DB.Boolean, nullable=True)
    has_uncollected = DB.Column(DB.Boolean, nullable=True)

    users = DB.relationship('User',
                            secondary=USER_ANNOUNCE_LINK,
                            backref='announcements')

    emails = DB.relationship('User',
                             secondary=EMAIL_ANNOUNCE_LINK,
                             lazy='dynamic')

    def __init__(self,
                 subject,
                 content,
                 sender,
                 send_email,
                 college=None,
                 affiliation=None,
                 has_tickets=None,
                 holds_ticket=None,
                 is_waiting=None,
                 has_collected=None,
                 has_uncollected=None,
                 use_noreply=False):
        self.timestamp = datetime.datetime.utcnow()
        self.subject = subject
        self.content = content
        self.sender = sender
        self.send_email = send_email
        self.use_noreply = use_noreply
        self.college = college
        self.affiliation = affiliation
        self.has_tickets = has_tickets
        self.holds_ticket = holds_ticket
        self.is_waiting = is_waiting
        self.has_collected = has_collected
        self.has_uncollected = has_uncollected

        recipient_query = user.User.query

        if self.college is not None:
            recipient_query = recipient_query.filter(
                user.User.college == self.college)

        if self.affiliation is not None:
            recipient_query = recipient_query.filter(
                user.User.affiliation == self.affiliation)

        for recipient in recipient_query.all():
            if ((  # pylint: disable=too-many-boolean-expressions
                    self.has_tickets is None
                    or recipient.has_tickets() == self.has_tickets)
                    and (self.holds_ticket is None
                         or recipient.has_held_ticket() == self.holds_ticket)
                    and (self.is_waiting is None
                         or recipient.is_waiting == self.is_waiting) and
                (self.has_collected is None
                 or recipient.has_collected_tickets() == self.has_collected)
                    and (self.has_uncollected is None or
                         (recipient.has_uncollected_tickets()
                          == self.has_uncollected))):
                self.users.append(recipient)
                if send_email:
                    self.emails.append(recipient)

    def __repr__(self):
        return '<Announcement {0}: {1}>'.format(self.object_id, self.subject)

    def send_emails(self, count):
        """Send the announcement as an email to a limited number of recipients.

        Used for batch sending, renders the text of the announcement into an
        email and sends it to users who match the criteria.

        Args:
            count: (int) Maximum number of emails to send

        Returns:
            (int) How much of the original limit is remaining (i.e. |count|
            minus the nuber of emails sent)
        """
        if self.use_noreply:
            sender = APP.config['EMAIL_FROM']
        else:
            sender = self.sender.email

        try:
            for recipient in self.emails:
                if count <= 0:
                    break

                APP.email_manager.send_text(recipient.email, self.subject,
                                            self.content, sender)

                self.emails.remove(recipient)
                count = count - 1
        finally:
            self.email_sent = (self.emails.count() == 0)
            DB.session.commit()

        return count
示例#15
0
class Ticket(DB.Model):
    """Model for tickets."""
    __tablename__ = 'ticket'
    object_id = DB.Column(DB.Integer, primary_key=True)

    ticket_type = DB.Column(DB.Unicode(50), nullable=False)

    paid = DB.Column(DB.Boolean(), default=False, nullable=False)
    entered = DB.Column(DB.Boolean(), default=False, nullable=False)
    cancelled = DB.Column(DB.Boolean(), default=False, nullable=False)

    price_ = DB.Column(DB.Integer(), nullable=False)
    note = DB.Column(DB.UnicodeText(), nullable=True)
    expires = DB.Column(DB.DateTime(), nullable=True)
    barcode = DB.Column(DB.Unicode(20), unique=True, nullable=True)
    claim_code = DB.Column(
        DB.Unicode(17),  # 3 groups of 5 digits separated by dashes
        nullable=True)
    claims_made = DB.Column(DB.Integer, nullable=False, default=0)
    owner_id = DB.Column(DB.Integer,
                         DB.ForeignKey('user.object_id'),
                         nullable=False)
    owner = DB.relationship('User',
                            backref=DB.backref('tickets',
                                               lazy='dynamic',
                                               order_by=b'Ticket.cancelled'),
                            foreign_keys=[owner_id])

    holder_id = DB.Column(DB.Integer,
                          DB.ForeignKey('user.object_id'),
                          nullable=True)
    holder = DB.relationship('User',
                             backref=DB.backref('held_ticket', uselist=False),
                             foreign_keys=[holder_id])
    holder_name = DB.Column(DB.String(60), nullable=True, default="Unassigned")

    def __init__(self, owner, ticket_type, price):
        self.owner = owner
        self.ticket_type = ticket_type
        self.price = price

        self.expires = (datetime.datetime.utcnow() +
                        APP.config['TICKET_EXPIRY_TIME'])

        self.claim_code = '-'.join(
            util.generate_key(5, string.digits)
            for _ in xrange(3)).decode('utf-8')

    def __repr__(self):
        return '<Ticket {0} owned by {1} ({2})>'.format(
            self.object_id, self.owner.full_name, self.owner.object_id)

    def can_be_cancelled(self):
        if datetime.datetime.utcnow() < datetime.datetime(
                2018, 5, 11) and not self.cancelled and not self.paid:
            return True
        return False

    def can_be_resold(self):
        return False

    def can_be_claimed(self):
        if self.status == 'Awaiting ticket holder.':
            return True
        return False

    def can_be_reclaimed(self):
        # if self.paid and datetime.datetime.utcnow()<datetime.datetime(2018,5,11) and not self.cancelled and self.holder_id==self.owner_id:
        #     return True
        return False

    def has_holder(self):
        if not self.holder_id == None:
            return True
        return False

    def can_be_paid_for(self):
        if self.paid == 0:
            return True
        return False

    def can_be_collected(self):
        if self.paid:
            return True
        return False

    def is_assigned(self):
        if self.holder_name == 'Unassigned':
            return False
        return True

    @staticmethod
    def get_by_claim_code(code):
        return Ticket.query.filter_by(claim_code=code).first()

    @property
    def price_pounds(self):
        """Get the price of this ticket as a string of pounds and pence."""
        price = '{0:03d}'.format(self.price)
        return price[:-2] + '.' + price[-2:]

    @property
    def transaction(self):
        """Get the transaction this ticket was paid for in."""
        for transaction_item in self.transaction_items:
            if transaction_item.transaction.paid:
                return transaction_item.transaction

        return None

    @property
    def payment_method(self):
        """Get the payment method for this ticket."""
        transaction = self.transaction

        if transaction:
            return transaction.payment_method
        else:
            return 'Unknown Payment Method'

    @property
    def price(self):
        """Get the price of the ticket."""
        return self.price_

    @property
    def status(self):
        """Get the status of this ticket."""
        if self.cancelled:
            return 'Cancelled.'
        elif not self.paid:
            return 'Awaiting payment. Expires {0}.'.format(
                self.expires.strftime('%H:%M %d/%m/%Y'))
        elif self.entered:
            return 'Used for entry.'
        elif self.collected and self.holder:
            return 'Ticket Sent to {0}.'.format(self.holder.full_name)
        elif self.collected:
            return 'Collected as {0}.'.format(self.barcode)
        # elif self.holder is None:
        #     return 'Awaiting ticket holder.'
        elif self.holder_name == 'Unassigned':
            return 'Awaiting ticket holder.'
        elif APP.config['REQUIRE_USER_PHOTO']:
            if not self.holder.photo.verified:
                return 'Awaiting verification of holder photo.'
        else:
            # return 'Held by {0}.'.format(self.holder.full_name)
            return 'Assigned to {0}.'.format(self.holder_name)

    @price.setter
    def price(self, value):
        """Set the price of the ticket."""
        self.price_ = max(value, 0)

        if self.price_ == 0:
            self.mark_as_paid()

    @hybrid.hybrid_property
    def collected(self):
        """Has this ticket been assigned a barcode."""
        return self.barcode != None  # pylint: disable=singleton-comparison

    def mark_as_paid(self):
        """Mark the ticket as paid, and clear any expiry."""
        self.paid = True
        self.expires = None

    def add_note(self, note):
        """Add a note to the ticket."""
        if not note.endswith('\n'):
            note = note + '\n'

        if self.note is None:
            self.note = note
        else:
            self.note = self.note + note

    @staticmethod
    def count():
        """How many tickets have been sold."""
        # TODO
        return Ticket.query.filter(Ticket.cancelled == False).count()  # pylint: disable=singleton-comparison

    @staticmethod
    def write_csv_header(csv_writer):
        """Write the header of a CSV export file."""
        csv_writer.writerow([
            'Ticket ID',
            'Ticket Type',
            'Paid',
            'Collected',
            'Entered',
            'Cancelled',
            'Price (Pounds)',
            'Holder\'s Name',
            'Notes',
            'Expires',
            'Barcode',
            'Owner\' User ID',
            'Owner\'s Name',
        ])

    def write_csv_row(self, csv_writer):
        """Write this object as a row in a CSV export file."""
        csv_writer.writerow([
            self.object_id,
            self.ticket_type,
            'Yes' if self.paid else 'No',
            'Yes' if self.collected else 'No',
            'Yes' if self.entered else 'No',
            'Yes' if self.cancelled else 'No',
            self.price_pounds,
            self.holder.full_name.encode('utf-8')
            if self.holder is not None else 'N/A',
            self.note,
            self.expires.strftime('%Y-%m-%d %H:%M:%S')
            if self.expires is not None else 'N/A',
            self.barcode if self.barcode is not None else 'N/A',
            self.owner_id,
            self.owner.full_name.encode('utf-8'),
        ])
示例#16
0
class Transaction(DB.Model):
    """Model for representing a monetary exchange transaction."""
    __tablename__ = 'transaction'
    object_id = DB.Column(DB.Integer, primary_key=True)

    payment_method = DB.Column(DB.Enum('Battels', 'Card', 'Free', 'Dummy'),
                               nullable=False)

    paid = DB.Column(DB.Boolean, default=False, nullable=False)
    created = DB.Column(DB.DateTime(), nullable=False)

    user_id = DB.Column(DB.Integer,
                        DB.ForeignKey('user.object_id'),
                        nullable=False)
    user = DB.relationship('User',
                           backref=DB.backref('transactions', lazy='dynamic'))

    __mapper_args__ = {'polymorphic_on': payment_method}

    def __init__(self, user, payment_method):
        self.user = user
        self.payment_method = payment_method

        self.created = datetime.datetime.utcnow()

    def __repr__(self):
        return '<Transaction {0}: {1} item(s)>'.format(self.object_id,
                                                       self.items.count())

    @property
    def value(self):
        """Get the total value of the transaction."""
        return sum(item.value for item in self.items)

    @property
    def value_pounds(self):
        """Get the total value of the transaction."""
        value_str = "{0:03d}".format(self.value)

        return value_str[:-2] + '.' + value_str[-2:]

    @property
    def tickets(self):
        """Get the tickets paid for in this transaction.

        Returns a list of Ticket objects.
        """
        return list(item.ticket for item in self.items
                    if item.item_type == 'Ticket')

    @property
    def postage(self):
        """Get the postage paid for in this transaction.

        Returns a single Postage object, or None.
        """
        try:
            return list(item.postage for item in self.items
                        if item.item_type == 'Postage')[0]
        except IndexError:
            return None

    @property
    def admin_fee(self):
        """Get the admin_fee paid for in this transaction.

        Returns a single AdminFee object, or None.
        """
        try:
            return list(item.admin_fee for item in self.items
                        if item.item_type == 'AdminFee')[0]
        except IndexError:
            return None

    def mark_as_paid(self):
        """Mark the transaction as paid for.

        Marks all tickets in the transaction as paid for.
        """
        self.paid = True

        for ticket in self.tickets:
            ticket.mark_as_paid()

        postage = self.postage
        if postage:
            postage.paid = True

        admin_fee = self.admin_fee
        if admin_fee:
            admin_fee.mark_as_paid()
示例#17
0
class Voucher(DB.Model):
    """Model for a discount voucher."""
    __tablename__ = 'voucher'
    object_id = DB.Column(DB.Integer, primary_key=True)

    code = DB.Column(DB.Unicode(30), nullable=False)
    expires = DB.Column(DB.DateTime(), nullable=True)
    discount_type = DB.Column(DB.Enum('Fixed Price', 'Fixed Discount',
                                      'Percentage Discount'),
                              nullable=False)
    discount_value = DB.Column(DB.Integer(), nullable=False)
    applies_to = DB.Column(DB.Enum('Ticket', 'Transaction'), nullable=False)
    single_use = DB.Column(DB.Boolean(), nullable=False)
    used = DB.Column(DB.Boolean(), default=False, nullable=True)

    used_by_id = DB.Column(DB.Integer,
                           DB.ForeignKey('user.object_id'),
                           nullable=True)
    used_by = DB.relationship('User',
                              backref=DB.backref('vouchers_used',
                                                 lazy='dynamic'))

    def __init__(self, code, expires, discount_type, discount_value,
                 applies_to, single_use):
        if discount_type not in [
                'Fixed Price', 'Fixed Discount', 'Percentage Discount'
        ]:
            raise ValueError(
                '{0} is not a valid discount type'.format(discount_type))

        if applies_to not in ['Ticket', 'Transaction']:
            raise ValueError(
                '{0} is not a valid application'.format(applies_to))

        self.code = code
        self.discount_type = discount_type
        self.discount_value = discount_value
        self.applies_to = applies_to
        self.single_use = single_use

        if isinstance(expires, datetime.timedelta):
            self.expires = datetime.datetime.utcnow() + expires
        else:
            self.expires = expires

    def __repr__(self):
        return '<Voucher: {0}/{1}>'.format(self.object_id, self.code)

    @staticmethod
    def get_by_code(code):
        """Get an Announcement object by a voucher code."""
        return Voucher.query.filter(Voucher.code == code).first()

    def apply(self, tickets, user):
        """Apply the voucher to a set of tickets.

        Checks if the voucher can be used, and applies its discount to the
        tickets.

        Args:
            tickets: (list(Ticket)) list of tickets to apply the voucher to
            user: (User) user who is using the voucher

        Returns:
            (bool, list(tickets), str/None) whether the voucher was applied, the
            mutated tickets, and an error message
        """
        if self.single_use and self.used:
            return (False, tickets, 'Voucher has already been used.')

        if (self.expires is not None
                and self.expires < datetime.datetime.utcnow()):
            return (False, tickets, 'Voucher has expired.')

        self.used = True
        if self.single_use:
            self.used_by = user

        if self.applies_to == 'Ticket':
            tickets[0] = self.apply_to_ticket(tickets[0])
            return (True, tickets, None)
        else:
            return (True, [self.apply_to_ticket(t) for t in tickets], None)

    def apply_to_ticket(self, ticket):
        """Apply the voucher to a single ticket.

        Recalculates the price of the ticket, and notes on the ticket that a
        voucher was used

        Args:
            ticket: (Ticket) the ticket to apply the voucher to

        Returns:
            (ticket) the mutated ticket
        """
        if self.discount_type == 'Fixed Price':
            ticket.price = self.discount_value
        elif self.discount_type == 'Fixed Discount':
            ticket.price = ticket.price - self.discount_value
        else:
            ticket.price = ticket.price * (100 - self.discount_value) / 100

        ticket.add_note('Used voucher {0}/{1}'.format(self.object_id,
                                                      self.code))

        return ticket