Esempio n. 1
0
class ProductVariant(Model):
    __tablename__ = "product_variant"
    sku = Column(db.String(32), unique=True)
    title = Column(db.String(255))
    price_override = Column(db.DECIMAL(10, 2), default=0.00)
    quantity = Column(db.Integer(), default=0)
    quantity_allocated = Column(db.Integer(), default=0)
    product_id = Column(db.Integer(), default=0)
    attributes = Column(MutableDict.as_mutable(db.JSON()))

    def __str__(self):
        return self.title or self.sku

    def display_product(self):
        return f"{self.product} ({str(self)})"

    @property
    def sku_id(self):
        return self.sku.split("-")[1]

    @sku_id.setter
    def sku_id(self, data):
        pass

    @property
    def is_shipping_required(self):
        return self.product.product_type.is_shipping_required

    @property
    def quantity_available(self):
        return max(self.quantity - self.quantity_allocated, 0)

    @property
    def is_in_stock(self):
        return self.quantity_available > 0

    @property
    def stock(self):
        return self.quantity - self.quantity_allocated

    @property
    def price(self):
        return self.price_override or self.product.price

    @property
    def product(self):
        return Product.get_by_id(self.product_id)

    def get_absolute_url(self):
        return url_for("product.show", id=self.product.id)

    @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

    def check_enough_stock(self, quantity):
        if self.stock < quantity:
            return False, f"{self.display_product()} has not enough stock"
        return True, "success"

    @staticmethod
    def clear_mc(target):
        rdb.delete(MC_KEY_PRODUCT_VARIANT.format(target.product_id))

    @classmethod
    def __flush_insert_event__(cls, target):
        super().__flush_insert_event__(target)
        target.clear_mc(target)

    @classmethod
    def __flush_after_update_event__(cls, target):
        super().__flush_after_update_event__(target)
        target.clear_mc(target)

    @classmethod
    def __flush_delete_event__(cls, target):
        super().__flush_delete_event__(target)
        target.clear_mc(target)
Esempio n. 2
0
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)