Exemplo n.º 1
0
class Cart(DB.Model):
    """Model for a cart."""
    __tablename__ = 'cart'
    object_id = DB.Column(DB.Integer, primary_key=True)

    user_id = DB.Column(DB.Integer,
                        DB.ForeignKey('user.object_id'),
                        nullable=True)
    user = DB.relationship('User', backref=DB.backref('cart', uselist=False))
    product_id = DB.Column(DB.Integer,
                           DB.ForeignKey('product.object_id'),
                           nullable=True)
    product = DB.relationship('Product',
                              backref=DB.backref('cart_product',
                                                 uselist=False))

    def __init__(self, user, product):
        self.user_id = user
        self.product_id = product

    def __repr__(self):
        return '<Products {0}: {1}>'.format('todo', 'todo')
class ProductTransactionItem(transaction_item.TransactionItem):
    """Model for representing a product in a transaction."""
    __tablename__ = 'product_transaction_item'
    __mapper_args__ = {'polymorphic_identity': 'Product'}
    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)

    product_id = DB.Column(DB.Integer,
                           DB.ForeignKey('product.object_id'),
                           nullable=False)
    product = DB.relationship('Product',
                              backref=DB.backref('transaction_items',
                                                 lazy='dynamic'))

    def __init__(self, transaction, product, is_refund=False):
        super(ProductTransactionItem, self).__init__(transaction, 'Product')

        self.product = product
        self.is_refund = is_refund

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

    @property
    def description(self):
        """Get a description of the transaction item."""
        return '{0}{1} Product ({2:05d})'.format(
            'Refund of ' if self.is_refund else '', self.product.name,
            self.product_id)
Exemplo n.º 3
0
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())
Exemplo n.º 4
0
class TransactionItem(DB.Model):
    """Model for representing an item in a transaction.

    Not used directly, use GenericTransactionItem, ProductTransactionItem,
    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',
            'Product',
            'Membership',
        ),
        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
Exemplo n.º 5
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)
    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',
        ])
Exemplo n.º 6
0
class Product(DB.Model):
    """Model for a products."""
    __tablename__ = 'product'
    object_id = DB.Column(DB.Integer, primary_key=True)

    name = DB.Column(DB.Unicode(50), unique=True, nullable=False)

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

    lot_of = DB.Column(DB.Integer(), nullable=False, default=1)

    description = DB.Column(DB.Text(), nullable=False)

    photo_id = DB.Column(DB.Integer,
                         DB.ForeignKey('photo.object_id'),
                         nullable=True)
    photo = DB.relationship('Photo',
                            backref=DB.backref('product', uselist=False))
    category_id = DB.Column(DB.Integer,
                            DB.ForeignKey('category.object_id'),
                            nullable=True)
    category = DB.relationship('Category',
                               backref=DB.backref('product_cat',
                                                  uselist=False))

    stock = 1000

    def __init__(self,
                 name,
                 category_id,
                 description="a thing",
                 price=1,
                 lot=1):
        self.name = name
        self.price = price
        self.category_id = category_id
        self.photo = None
        self.description = description
        self.lot_of = lot

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

    @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 price(self):
        """Get the price of the membership."""
        return self.price_

    @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()
Exemplo n.º 7
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', '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()
Exemplo n.º 8
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])

    memberships = DB.relationship('Membership',
                                  secondary=LOG_MEMBERSHIP_LINK,
                                  backref=DB.backref('events', lazy='dynamic'),
                                  lazy='dynamic')
    products = DB.relationship('Product',
                               secondary=LOG_PRODUCT_LINK,
                               backref=DB.backref('events2', lazy='dynamic'),
                               lazy='dynamic')

    transaction_id = DB.Column(DB.Integer(),
                               DB.ForeignKey('transaction.object_id'),
                               nullable=True)
    transaction = DB.relationship('Transaction',
                                  backref=DB.backref('events3',
                                                     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,
                 memberships=None,
                 products=None,
                 transaction=None,
                 purchase_group=None,
                 admin_fee=None):
        if memberships is None:
            memberships = []
        if products is None:
            products = []

        self.timestamp = datetime.datetime.utcnow()
        self.ip_address = ip_address
        self.action = action
        self.actor = actor
        self.user = user
        self.memberships = memberships
        self.products = products
        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 Membership 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(membership.object_id) for membership in self.memberships),
            # (
            #     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'
            # ),
        ])
Exemplo n.º 9
0
# coding: utf-8
"""Database model for log entries persisted to the database."""

from __future__ import unicode_literals

import datetime

from flaskshop.database import db
#DB = db.DB
from flaskshop.app import oussshopdb as DB

LOG_MEMBERSHIP_LINK = DB.Table(
    'log_membership_link', DB.Model.metadata,
    DB.Column('log_id', DB.Integer, DB.ForeignKey('log.object_id')),
    DB.Column('membership_id', DB.Integer,
              DB.ForeignKey('membership.object_id')))
LOG_PRODUCT_LINK = DB.Table(
    'log_product_link', DB.Model.metadata,
    DB.Column('log_id', DB.Integer, DB.ForeignKey('log.object_id')),
    DB.Column('product_id', DB.Integer, DB.ForeignKey('product.object_id')))


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())
Exemplo n.º 10
0
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'),
        ])