class EwayTransaction(DB.Model): """Model for representing an eWay Transaction.""" __tablename__ = 'eway_transaction' object_id = DB.Column(DB.Integer, primary_key=True) # This holds the order_id access_code = DB.Column(DB.Unicode(200), nullable=False) charged = DB.Column(DB.Integer(), nullable=False) completed = DB.Column(DB.DateTime(), nullable=True) result_code = DB.Column(DB.Unicode(2), nullable=True) # This holds the PASREF field for realex eway_id = DB.Column(DB.String(50), nullable=True) refunded = DB.Column(DB.Integer(), nullable=False, default=0) def __init__(self, access_code, charged): self.access_code = access_code self.charged = charged @property def status(self): """Get a better representation of the status of this transaction. The eWay API returns statuses as 2 digit codes; this function provides a mapping from these codes to a boolean success value and associated explanation. Returns: (bool, str) pair of success value and explanation """ try: return REALEX_RESULT_CODES[self.result_code[0]] except KeyError as err: return (False, 'Unknown response: {0}'.format(err.args[0])) @property def success(self): """Get whether the transaction was completed successfully.""" success = self.status[0] if success is None: return 'Uncompleted' elif success: return 'Successful' else: return 'Unsuccessful'
class PayPalTransaction(transaction.Transaction): """Model for representing a paypal transaction.""" __tablename__ = 'paypal_transaction' __mapper_args__ = {'polymorphic_identity': 'PayPal'} object_id = DB.Column(DB.Integer, primary_key=True) object_id = DB.Column(DB.Integer(), DB.ForeignKey('transaction.object_id'), primary_key=True) # This holds the order_id access_code = DB.Column(DB.Unicode(200), nullable=True) charged = DB.Column(DB.Integer(), nullable=False, default=0) completed = DB.Column(DB.DateTime(), nullable=True) result_code = DB.Column(DB.Unicode(2), nullable=True) # This holds the PASREF field for realex paypal_id = DB.Column(DB.String(50), nullable=True) refunded = DB.Column(DB.Integer(), nullable=False, default=0) order_id = DB.Column(DB.Integer(), nullable=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(PayPalTransaction, self).__init__(user, 'PayPal') # if eway_transaction is not None: # self.eway_transaction = eway_trans def __repr__(self): return '<PayPalTransaction({0}): {1} item(s)>'.format( self.object_id, self.items.count())
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) prepaid_classes = DB.Column(DB.Integer, nullable=False, default=0) 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_membership(self): if membership.Membership.query.filter_by( owner_id=self.object_id).count() > 0: return True return False def memberships(self): if self.has_membership(): return membership.Membership.query.filter_by( owner_id=self.object_id).all() return None def has_unpaid_memberships(self): if membership.Membership.query.filter_by(owner_id=self.object_id, paid=0).count() > 0: return True return False def has_paid_memberships(self): if membership.Membership.query.filter_by(owner_id=self.object_id, paid=1).count() > 0: return True return False def can_claim_membership(self): return False #todo: check logic # def has_collected_memberships(self): # return False # def has_uncollected_memberships(self): # return False def has_held_membership(self): # if membership.Membership.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, membership_type_slug): return 0 @property def total_group_purchase_requested(self): return 0 @property def total_group_purchase_value(self): return 0 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) # return 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) # self.password_hash = 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 memberships(self): """Get the active memberships owned by the user.""" return self.memberships.filter(membership.Membership.cancelled == False # pylint: disable=singleton-comparison ) @property def active_membership_count(self): """How many active memberships does the user own?""" return self.active_memberships.count() @property def waiting_for(self): return 0 @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, memberships, 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', ])
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', 'PayPal', '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.""" if self.items.count() >= APP.config['GROUP_SIZE']: if self.items.count() >= APP.config['GROUP_SIZE2']: return sum(1600 for item in self.items) #APP.config['GROUP_TICKET_PRICE_p'] else: return sum(1800 for item in self.items) #APP.config['GROUP_TICKET_PRICE_p'] 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:] def value_pounds_surcharge(self, surcharge=0): """Get the total value of the transaction.""" value_str = "{0:03d}".format(self.value + surcharge) 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()
class Membership(DB.Model): """Model for membership.""" __tablename__ = 'membership' object_id = DB.Column(DB.Integer, primary_key=True) membership_type = DB.Column(DB.Unicode(50), nullable=False) owner_id = DB.Column(DB.Integer, DB.ForeignKey('user.object_id'), nullable=False) owner = DB.relationship('User', backref=DB.backref( 'memberships', lazy='dynamic', order_by=b'Membership.cancelled'), foreign_keys=[owner_id]) paid = 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) def __init__(self, owner, membership_type, price): self.owner = owner self.membership_type = membership_type self.price = price y = datetime.datetime.utcnow().year m = datetime.datetime.utcnow().month if m >= 10: y = y + 1 self.expires = datetime.datetime(y, 10, 1) #(datetime.datetime.utcnow()) + #APP.config['MEMBERSHIP_EXPIRY_TIME']) def generate_barcode(self): # generate barcode key = util.generate_key(20).decode('utf-8') self.barcode = key def generate_qrcode(self): # generate QR qrcode_img = pyqrcode.create( '{0}admin/membership/validate-ticket/{1}/{2}'.format( APP.config['EISITIRIO_URL'], self.object_id, self.barcode)) buffer = io.BytesIO() qrcode_img.png(buffer, scale=20) f = open( '/Users/Gwyneth/Documents/repositories/oussshop/flaskshop/tmp.png', 'wb') f.write(buffer) f.close() return buffer.getvalue() def __repr__(self): return '<Membership {0} owned by {1} ({2})>'.format( self.object_id, self.owner.full_name, self.owner.object_id) def can_be_cancelled(self): return False def can_be_resold(self): return False def can_be_claimed(self): return False def can_be_reclaimed(self): return False def has_holder(self): return False def can_be_paid_for(self): if self.paid == 0: return True return False def can_be_collected(self): return False def is_assigned(self): return True @property def price_pounds(self): """Get the price of this membership 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 membership 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 membership.""" transaction = self.transaction if transaction: return transaction.payment_method else: return 'Unknown Payment Method' @property def price(self): """Get the price of the membership.""" return self.price_ @property def status(self): """Get the status of this membership.""" if not self.paid: return 'Awaiting payment. Expires {0}.'.format( self.expires.strftime('%H:%M %d/%m/%Y')) elif APP.config['REQUIRE_USER_PHOTO']: if not self.holder.photo.verified: return 'Awaiting verification of holder photo.' else: return 'Valid membership' @price.setter def price(self, value): """Set the price of the membership.""" self.price_ = max(value, 0) if self.price_ == 0: self.mark_as_paid() @hybrid.hybrid_property def collected(self): """Has this membership been assigned a barcode.""" return self.barcode != None # pylint: disable=singleton-comparison def mark_as_paid(self): """Mark the membership as paid, and clear any expiry.""" self.paid = True self.expires = None def add_note(self, note): """Add a note to the membership.""" 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 memberships have been sold.""" # TODO return Membership.query.filter(Membership.paid == True).count() # pylint: disable=singleton-comparison @staticmethod def write_csv_header(csv_writer): """Write the header of a CSV export file.""" csv_writer.writerow([ 'Membership ID', 'Membership Type', 'Paid', 'Cancelled', 'Price (Pounds)', '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.membership_type, 'Yes' if self.paid else 'No', 'Yes' if self.cancelled else 'No', self.price_pounds, 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'), ])