class Message(db.Model): """Database model for messages in a thread. Contains: - id: int, auto-incremented. - thread_id: int, foreign key. - sender_id: int, foreign key. No need for receiver because thread has info. - date: date. - text: string. - seen: date. """ __tablename__ = 'messages' id = db.Column(db.Integer, primary_key=True) thread_id = db.Column(db.Integer, db.ForeignKey('message_threads.id')) sender_id = db.Column(db.Integer, db.ForeignKey('users.id')) text = db.Column(db.String, nullable=False) date = db.Column(db.DateTime, default=datetime.datetime.utcnow) seen = db.Column(db.DateTime) def __init__(self, thread_id, sender_id, text): self.thread_id = thread_id self.sender_id = sender_id self.text = text def see(self, userId): thread = self.thread.first() if userId != self.sender_id and (thread.user1 == userId or thread.user2 == userId): self.seen = datetime.datetime.utcnow()
class Report(db.Model): """Database model for reports to admins. Contains: - id: int, auto-incremented. - sender_id: int, foreign key. - about_id: int, foreign key. - reason: int, foreign key. - date: date. """ __tablename__ = 'reports' id = db.Column(db.Integer, primary_key=True) sender_id = db.Column(db.Integer, db.ForeignKey('users.id')) about_id = db.Column(db.Integer, db.ForeignKey('users.id')) reason_id = db.Column(db.Integer, db.ForeignKey('reasons.id')) message = db.Column(db.String, nullable=False) date = db.Column(db.DateTime, default=datetime.datetime.utcnow) resolved = db.Column(db.Boolean, default=False) def __init__(self, sender_id, about_id, reason_id, message): self.sender_id = sender_id self.about_id = about_id self.reason_id = reason_id self.message = message def getSender(self): return User.query.filter_by(id=self.sender_id).first() def getAbout(self): return User.query.filter_by(id=self.about_id).first()
class Upload(db.Model): """Database model for product-related uploads (photo, video). Contains: - id: int, auto-increment. - filename: string. - date: upload date. - product_id: int, foreign key. - variety_id: int, foreign key. Optional. - order: int, default: 0 """ __tablename__ = 'uploads' id = db.Column(db.Integer, primary_key=True) product_id = db.Column(db.Integer, db.ForeignKey('products.id')) filename = db.Column(db.String) date = db.Column(db.DateTime, default=datetime.datetime.utcnow) variety_id = db.Column(db.Integer, db.ForeignKey('varieties.id')) order = db.Column(db.Integer, default=0) def __init__(self, filename, product_id, variety_id=None, order=0): self.filename = filename self.product_id = product_id self.variety_id = variety_id self.order = order def url(self): return get_upload_url(self.filename)
class MessageThread(db.Model): """Database model for message threads. Contains: - id: int, auto-incremented. - user1: int, foreign key. - user2: int, foreign key. - title: string. Optional. - order_id: int, foreign key (if about an order). """ __tablename__ = 'message_threads' id = db.Column(db.Integer, primary_key=True) user1 = db.Column(db.Integer, db.ForeignKey('users.id')) user2 = db.Column(db.Integer, db.ForeignKey('users.id')) title = db.Column(db.String, nullable=True) order_id = db.Column(db.Integer, db.ForeignKey('orders.id')) messages = db.relationship('Message', backref='thread', lazy='dynamic') def __init__(self, user1, user2, title=None): self.user1 = user1 self.user2 = user2 self.title = title def isParticipant(self, user): return self.user1 == user or self.user2 == user def otherUser(self, user): if self.isParticipant(user): return self.user2 if self.user1 == user else self.user1 return None
class Order(db.Model): """Database model for orders. Contains: - id: int, auto-increment. - user_id: int, foreign key. - create_date: date. - update_date: date. - status: string. - address_id: int, foreign key. """ __tablename__ = 'orders' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id')) create_date = db.Column(db.DateTime, default=datetime.datetime.utcnow) update_date = db.Column(db.DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow) status = db.Column(db.String, default='New') address_id = db.Column(db.Integer, db.ForeignKey('addresses.id')) products = db.relationship('Product', secondary='order_product',\ back_populates='orders', lazy='dynamic') message_threads = db.relationship('MessageThread', backref='order', lazy='dynamic') def __init__(self, user_id): self.user_id = user_id
class Address(db.Model): """Database model for addresses (physical). Contains: - id: int, auto-incremented. - name: name to assign to this address (every user can have multiple addresses). - user_id: int, foreign key. - country_id: string, ISO 3166-1 code, foreign key. - city: string. - zip/postal code: string. - phone: string. - text: string. """ __tablename__ = 'addresses' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String, default='Default') user_id = db.Column(db.Integer, db.ForeignKey('users.id')) country_id = db.Column(db.String(2), db.ForeignKey('countries.id')) code = db.Column(db.String) _phone = db.Column(db.String) text = db.Column(db.String, nullable=False) orders = db.relationship('Order', backref='address', lazy='dynamic') def __init__(self, name, user_id, text, country_id, code=None, phone=None): self.name = name self.user_id = user_id self.country_id = country_id self.code = code self.phone = phone self.text = text @property def phone(self): return self._phone @phone.setter def phone(self, value): pattern = re.compile( '[^\d\+x]') # all but digits, +, and x (for extensions) stripped = re.sub(pattern, '', value) self._phone = stripped @validates('name') def validate_name(self, key, name_input): """Makes sure the name doesn't have any numbers or special chars. Raises a DBException otherwise. """ validate_name_pattern(name_input, allowNumbers=True) return name_input
class Variety(db.Model): """Database model for varieties in products (sizes, etc). Contains: - id: int, auto-incremented. - product_id: int, foreign key. - name: string. What is this variety? (e.g. Size small) No validation. - price: float. - available: boolean, default: True. """ __tablename__ = 'varieties' id = db.Column(db.Integer, primary_key=True) product_id = db.Column(db.Integer, db.ForeignKey('products.id')) name = db.Column(db.String, nullable=False) price = db.Column(db.Float, nullable=True) available = db.Column(db.Boolean, default=True) uploads = db.relationship('Upload', backref='variety', lazy='dynamic') orders = db.relationship('OrderProduct', backref='variety', lazy='dynamic') def __init__(self, name, product_id, price=None, available=True): self.name = name self.product_id = product_id self.price = price self.available = available @validates('price') def validate_price(self, key, pr): if pr is not None and pr < 0: raise DBException({ 'message': 'Variety price cannot be less than zero.', 'code': 'price' }) return pr
class ProductCategory(db.Model): """Database model for the relationship between products and categories (many-to-many). Contains: - product_id: int, foreign key. - category_id: int, foreign key. """ __tablename__ = 'product_category' product_id = db.Column(db.Integer, db.ForeignKey('products.id'), primary_key=True) category_id = db.Column(db.Integer, db.ForeignKey('categories.id'), primary_key=True) def __init__(self, product_id, category_id): self.product_id = product_id self.category_id = category_id
class Admins(db.Model): """Database model for admins. Contains: - id: int, auto-incremented. - user_id: int, foreign key. - date: date. """ __tablename__ = 'admins' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), unique=True) def __init__(self, user_id): self.user_id = user_id
class Review(db.Model): """Database model for reviews on products. Contains: - id: int, auto-increment. - user_id: int, foreign key. - product_id: int, foreign key. - rating: float. - title: string. - text: string. - create_date: review date. - update_date: update date. """ __tablename__ = 'reviews' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id')) product_id = db.Column(db.Integer, db.ForeignKey('products.id')) rating = db.Column(db.Float, nullable=False) title = db.Column(db.String) text = db.Column(db.String) create_date = db.Column(db.DateTime, default=datetime.datetime.utcnow) update_date = db.Column(db.DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow) def __init__(self, user_id, product_id, rating, title=None, text=None): self.user_id = user_id self.product_id = product_id self.rating = rating self.title = title self.text = text @validates('rating') def validate_rating(self, key, r): """Makes sure the rating is between 1 and 5 with 0.5 increments only.""" half_or_full = r % 1 == 0 or r % 1 == 0.5 if r < 1 or r > 5 or not half_or_full: raise DBException({'message': 'Rating must be between 1 and 5, with 0.5 increments only.',\ 'code': 'rating'}) return r
class OrderProduct(db.Model): """Database model for relationship between orders and models (many-to-many). Contains: - id: int, auto-incremented. - order_id: int, foreign key. - product_id: int, foreign key. - variety_id: int, foreign key. - quantity: int. - create_date: date. - update_date: date. """ __tablename__ = 'order_product' id = db.Column(db.Integer, primary_key=True) order_id = db.Column(db.Integer, db.ForeignKey('orders.id')) product_id = db.Column(db.Integer, db.ForeignKey('products.id')) variety_id = db.Column(db.Integer, db.ForeignKey('varieties.id')) quantity = db.Column(db.Integer, nullable=False, default=0) create_date = db.Column(db.DateTime, default=datetime.datetime.utcnow) update_date = db.Column(db.DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow) def __init__(self, order_id, product_id, variety_id, quantity=0): self.order_id = order_id self.product_id = product_id self.variety_id = variety_id self.quantity = quantity @validates('quantity') def validate_quantity(self, key, q): """Validate that quantity is more than 0.""" if q < 0: raise DBException({ 'message': 'Quantity cannot be less than zero.', 'code': 'quantity' }) return q
class Product(db.Model): """Database model for products. Contains: - id: int, auto-incremented. - name: string. - seller_id: int, foreign key. - update_date: date. - create_date: date. - description: string. - price: float. - available: boolean, default: True. """ __tablename__ = 'products' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String, nullable=False) seller_id = db.Column(db.Integer, db.ForeignKey('users.id')) create_date = db.Column(db.DateTime, default=datetime.datetime.utcnow) update_date = db.Column(db.DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow) description = db.Column(db.String) currency_id = db.Column(db.String(3), db.ForeignKey('currencies.id')) price = db.Column(db.Float) categories = db.relationship('Category', secondary='product_category',\ back_populates='products', lazy='dynamic') varieties = db.relationship('Variety', backref='product', lazy='dynamic',\ cascade='save-update, merge, delete') uploads = db.relationship('Upload', backref='product', lazy='dynamic',\ cascade='save-update, merge, delete') reviews = db.relationship('Review', backref='product', lazy='dynamic',\ cascade='save-update, merge, delete') orders = db.relationship('Order', secondary='order_product',\ back_populates='products', lazy='dynamic') available = db.Column(db.Boolean, default=True, nullable=False) force_unavailable = db.Column(db.Boolean, default=False, nullable=False) def __init__(self, name, seller_id, description=None, price=None, currency_id=None): self.name = name self.seller_id = seller_id self.description = description self.price = price self.currency_id = currency_id def random_picture(self): return get_upload_url( self.uploads.order_by(func.random()).first().filename) def avg_rating(self): return db.session.query(func.avg(Review.rating).label('average')).\ join(Product).filter(Product.id == self.id).first()[0] @validates('price') def validate_price(self, key, p): """Makes sure the price is not less than 0.""" if p is not None and p < 0: raise DBException({ 'message': 'Default price cannot be less than zero.', 'code': 'price' }) return p