class Page(Model): __tablename__ = "public_page" title = Column(db.String(255), nullable=False) slug = Column(db.String(255)) content = Column(db.Text()) is_visible = Column(db.Boolean(), default=True) if Config.USE_REDIS: content = PropsItem("content", "") def get_absolute_url(self): identity = self.slug or self.id return url_for("public.show_page", identity=identity) @classmethod @cache(MC_KEY_PAGE_ID.format("{identity}")) def get_by_identity(cls, identity): try: int(identity) except ValueError: return Page.query.filter(Page.slug == identity).first() return Page.get_by_id(identity) @property def url(self): return self.get_absolute_url() def __str__(self): return self.title @classmethod def __flush_after_update_event__(cls, target): super().__flush_after_update_event__(target) rdb.delete(MC_KEY_PAGE_ID.format(target.id)) rdb.delete(MC_KEY_PAGE_ID.format(target.slug))
class PluginRegistry(Model): __tablename__ = "plugin_registry" name = Column(db.String(100), unique=True) enabled = Column(db.Boolean(), default=True) @property def info(self): return current_app.pluggy.plugin_metadata.get(self.name, {})
class OrderLine(Model): __tablename__ = "order_line" product_name = Column(db.String(255)) product_sku = Column(db.String(100)) quantity = Column(db.Integer()) unit_price_net = Column(db.DECIMAL(10, 2)) is_shipping_required = Column(db.Boolean(), default=True) order_id = Column(db.Integer()) variant_id = Column(db.Integer()) @property def variant(self): return ProductVariant.get_by_id(self.variant_id) def get_total(self): return self.unit_price_net * self.quantity
class OrderNote(Model): __tablename__ = "order_note" order_id = Column(db.Integer()) user_id = Column(db.Integer()) content = Column(db.Text()) is_public = Column(db.Boolean(), default=True)
class ProductType(Model): __tablename__ = "product_type" title = Column(db.String(255), nullable=False) has_variants = Column(db.Boolean(), default=True) is_shipping_required = Column(db.Boolean(), default=False) def __str__(self): return self.title @property def product_attributes(self): at_ids = (ProductTypeAttributes.query.with_entities( ProductTypeAttributes.product_attribute_id).filter( ProductTypeAttributes.product_type_id == self.id).all()) return ProductAttribute.query.filter( ProductAttribute.id.in_(id for id, in at_ids)).all() @property def variant_attributes(self): at_ids = (ProductTypeVariantAttributes.query.with_entities( ProductTypeVariantAttributes.product_attribute_id).filter( ProductTypeVariantAttributes.product_type_id == self.id).all()) return ProductAttribute.query.filter( ProductAttribute.id.in_(id for id, in at_ids)).all() @property def variant_attr_id(self): if self.variant_attributes: return self.variant_attributes[0].id else: return None def update_product_attr(self, new_attrs): origin_ids = (ProductTypeAttributes.query.with_entities( ProductTypeAttributes.product_attribute_id).filter_by( product_type_id=self.id).all()) origin_ids = set(i for i, in origin_ids) new_attrs = set(int(i) for i in new_attrs) need_del = origin_ids - new_attrs need_add = new_attrs - origin_ids for id in need_del: ProductTypeAttributes.query.filter_by( product_type_id=self.id, product_attribute_id=id).first().delete(commit=False) for id in need_add: new = ProductTypeAttributes(product_type_id=self.id, product_attribute_id=id) db.session.add(new) db.session.commit() def update_variant_attr(self, variant_attr): origin_attr = ProductTypeVariantAttributes.query.filter_by( product_type_id=self.id).first() if origin_attr: origin_attr.product_attribute_id = variant_attr origin_attr.save() else: ProductTypeVariantAttributes.create( product_type_id=self.id, product_attribute_id=variant_attr) def delete(self): need_del_product_attrs = ProductTypeAttributes.query.filter_by( product_type_id=self.id).all() need_del_variant_attrs = ProductTypeVariantAttributes.query.filter_by( product_type_id=self.id).all() for item in itertools.chain(need_del_product_attrs, need_del_variant_attrs): item.delete(commit=False) need_update_products = Product.query.filter_by( product_type_id=self.id).all() for product in need_update_products: product.product_type_id = 0 db.session.add(product) db.session.delete(self) db.session.commit()
class Product(Model): __tablename__ = "product_product" title = Column(db.String(255), nullable=False) on_sale = Column(db.Boolean(), default=True) rating = Column(db.DECIMAL(8, 2), default=5.0) sold_count = Column(db.Integer(), default=0) review_count = Column(db.Integer(), default=0) basic_price = Column(db.DECIMAL(10, 2)) category_id = Column(db.Integer()) is_featured = Column(db.Boolean(), default=False) product_type_id = Column(db.Integer()) attributes = Column(MutableDict.as_mutable(db.JSON())) description = Column(db.Text()) if Config.USE_REDIS: description = PropsItem("description") def __str__(self): return self.title def __iter__(self): return iter(self.variants) def get_absolute_url(self): return url_for("product.show", id=self.id) @property @cache(MC_KEY_PRODUCT_IMAGES.format("{self.id}")) def images(self): return ProductImage.query.filter( ProductImage.product_id == self.id).all() @property def first_img(self): if self.images: return str(self.images[0]) return "" @property def is_in_stock(self): return any(variant.is_in_stock for variant in self) @property def category(self): return Category.get_by_id(self.category_id) @property def product_type(self): return ProductType.get_by_id(self.product_type_id) @property def is_discounted(self): if float(self.discounted_price) > 0: return True return False @property @cache(MC_KEY_PRODUCT_DISCOUNT_PRICE.format("{self.id}")) def discounted_price(self): from flaskshop.discount.models import Sale return Sale.get_discounted_price(self) @property def price(self): if self.is_discounted: return self.basic_price - self.discounted_price return self.basic_price @property def price_human(self): return "$" + str(self.price) @property def on_sale_human(self): return "Y" if self.on_sale else "N" @property @cache(MC_KEY_PRODUCT_VARIANT.format("{self.id}")) def variant(self): return ProductVariant.query.filter( ProductVariant.product_id == self.id).all() @property def attribute_map(self): items = { ProductAttribute.get_by_id(k): AttributeChoiceValue.get_by_id(v) for k, v in self.attributes.items() } return items @classmethod # @cache(MC_KEY_FEATURED_PRODUCTS.format("{num}")) def get_featured_product(cls, num=8): # 首頁的 featured products return cls.query.filter_by(is_featured=True).limit(num).all() def update_images(self, new_images): origin_ids = (ProductImage.query.with_entities( ProductImage.product_id).filter_by(product_id=self.id).all()) origin_ids = set(i for i, in origin_ids) new_images = set(int(i) for i in new_images) need_del = origin_ids - new_images need_add = new_images - origin_ids for id in need_del: ProductImage.get_by_id(id).delete(commit=False) for id in need_add: image = ProductImage.get_by_id(id) image.product_id = self.id image.save(commit=False) db.session.commit() def update_attributes(self, attr_values): attr_entries = [ str(item.id) for item in self.product_type.product_attributes ] attributes = dict(zip(attr_entries, attr_values)) self.attributes = attributes def generate_variants(self): if not self.product_type.has_variants: ProductVariant.create(sku=str(self.id) + "-1337", product_id=self.id) else: sku_id = 1337 variant_attributes = self.product_type.variant_attributes[0] for value in variant_attributes.values: sku = str(self.id) + "-" + str(sku_id) attributes = {str(variant_attributes.id): str(value.id)} ProductVariant.create( sku=sku, title=value.title, product_id=self.id, attributes=attributes, ) sku_id += 1 def delete(self): need_del_collection_products = ProductCollection.query.filter_by( product_id=self.id).all() for item in itertools.chain(self.images, self.variant, need_del_collection_products): item.delete(commit=False) db.session.delete(self) db.session.commit() @staticmethod def clear_mc(target): rdb.delete(MC_KEY_PRODUCT_DISCOUNT_PRICE.format(target.id)) keys = rdb.keys(MC_KEY_FEATURED_PRODUCTS.format("*")) for key in keys: rdb.delete(key) @staticmethod def clear_category_cache(target): keys = rdb.keys( MC_KEY_CATEGORY_PRODUCTS.format(target.category_id, "*")) for key in keys: rdb.delete(key) @classmethod def __flush_insert_event__(cls, target): super().__flush_insert_event__(target) if current_app.config["USE_ES"]: from flaskshop.public.search import Item Item.add(target) @classmethod def __flush_before_update_event__(cls, target): super().__flush_before_update_event__(target) target.clear_category_cache(target) @classmethod def __flush_after_update_event__(cls, target): super().__flush_after_update_event__(target) target.clear_mc(target) target.clear_category_cache(target) if current_app.config["USE_ES"]: from flaskshop.public.search import Item Item.update_item(target) @classmethod def __flush_delete_event__(cls, target): from flaskshop.public.search import Item super().__flush_delete_event__(target) target.clear_mc(target) target.clear_category_cache(target) Item.delete(target)
class User(Model, UserMixin): __tablename__ = "account_user" username = Column(db.String(80), unique=True, nullable=False, comment="user`s name") email = Column(db.String(80), unique=True, nullable=False) #: The hashed password _password = Column("password", db.String(128)) nick_name = Column(db.String(255)) is_active = Column(db.Boolean(), default=False) open_id = Column(db.String(80), index=True) session_key = Column(db.String(80), index=True) def __init__(self, username, email, password, **kwargs): super().__init__(username=username, email=email, password=password, **kwargs) def __str__(self): return self.username @hybrid_property def password(self): return self._password @password.setter def password(self, value): self._password = bcrypt.generate_password_hash(value) @property def avatar(self): return Gravatar(self.email).get_image() def check_password(self, value): """Check password.""" return bcrypt.check_password_hash(self.password, value) @property def addresses(self): return UserAddress.query.filter_by(user_id=self.id) @property def is_active_human(self): return "Y" if self.is_active else "N" @property def roles(self): at_ids = ( UserRole.query.with_entities(UserRole.role_id) .filter_by(user_id=self.id) .all() ) return Role.query.filter(Role.id.in_(id for id, in at_ids)).all() def delete(self): for addr in self.addresses: addr.delete() return super().delete() def can(self, permissions): if not self.roles: return False all_perms = reduce(or_, map(lambda x: x.permissions, self.roles)) return all_perms & permissions == permissions def can_admin(self): return self.can(Permission.ADMINISTER) def can_edit(self): return self.can(Permission.EDITOR)