Exemple #1
0
def cached_product_queryset(queryset, request, category, data):
    """
    Returns the cached queryset or cache it when needed
    Note: this method returns a list of Product instances
    rtype: list[Product]
    """
    key_data = OrderedDict()
    for k, v in data.items():
        if isinstance(v, list):
            v = "|".join(v)
        key_data[k] = v

    item = "product_queryset:"

    if request.customer.is_all_seeing:
        item = "%sU%s" % (item, request.user.pk)
    if category:
        item = "%sC%s" % (item, category.pk)

    key, products = context_cache.get_cached_value(
        identifier="product_queryset",
        item=item,
        allow_cache=True,
        context=request,
        data=key_data
    )

    if products is not None:
        return products

    products = list(queryset)
    context_cache.set_cached_value(key, products)
    return products
    def get_fields(self, request, category=None):
        if not category:
            return

        key, val = context_cache.get_cached_value(
            identifier="productvariationfilter", item=self, context=request, category=category)
        if val:
            return val

        variation_values = defaultdict(set)
        for variation in ProductVariationVariable.objects.filter(
                Q(product__shop_products__categories=category),
                ~Q(product__shop_products__visibility=ShopProductVisibility.NOT_VISIBLE)):
            for value in variation.values.all():
                # TODO: Use ID here instead of this "trick"
                choices = (value.value.replace(" ", "*"), value.value)
                variation_values[slugify(variation.name)].add(choices)

        fields = []
        for variation_key, choices in six.iteritems(variation_values):
            fields.append((
                "variation_%s" % variation_key,
                forms.MultipleChoiceField(
                    choices=choices, required=False, label=capfirst(variation_key), widget=FilterWidget())
            ))
        context_cache.set_cached_value(key, fields)
        return fields
Exemple #3
0
    def __call__(self, context, product, quantity=1, allow_cache=True):
        """
        :type product: shuup.core.models.Product
        """

        key, val = context_cache.get_cached_value(
            identifier=self.cache_identifier, item=product, context=context.get('request', context),
            quantity=quantity, name=self.name, allow_cache=allow_cache)
        if val is not None:
            return val

        options = PriceDisplayOptions.from_context(context)
        if options.hide_prices:
            val = ("", "")
            context_cache.set_cached_value(key, val)
            return val

        request = context.get('request')
        priced_children = product.get_priced_children(request, quantity)
        priced_products = priced_children if priced_children else [
            (product, _get_priceful(request, product, quantity))]

        def get_formatted_price(priced_product):
            (prod, price_info) = priced_product
            if not price_info:
                return ""
            pf = convert_taxness(request, prod, price_info, options.include_taxes)
            price = money(pf.price)
            return price

        min_max = (priced_products[0], priced_products[-1])
        prices = tuple(get_formatted_price(x) for x in min_max)
        context_cache.set_cached_value(key, prices)
        return prices
Exemple #4
0
    def get_variations(self, shop_product):
        key, val = context_cache.get_cached_value(identifier="variations",
                                                  item=shop_product,
                                                  context={"customer": self.context["customer"]},
                                                  allow_cache=True)
        if val is not None:
            return val
        data = []
        combinations = list(shop_product.product.get_all_available_combinations() or [])
        if not combinations and shop_product.product.mode == ProductMode.SIMPLE_VARIATION_PARENT:
            for product_pk, sku in Product.objects.filter(
                    variation_parent_id=shop_product.product_id).values_list("pk", "sku"):
                combinations.append({
                    "result_product_pk": product_pk,
                    "sku_part": sku,
                    "hash": None,
                    "variable_to_value": {}
                })

        qs = get_shop_product_queryset(False).filter(
            shop_id=shop_product.shop_id, product__pk__in=[combo["result_product_pk"] for combo in combinations])
        products = self.children_serializer(qs, many=True, context=self.context).data
        product_map = {product["product_id"]: product for product in products}
        for combination in combinations:
            child = product_map.get(combination["result_product_pk"])
            data.append({
                "product": child or {},
                "sku_part": combination["sku_part"],
                "hash": combination["hash"],
                "combination": {
                    force_text(k): force_text(v) for k, v in six.iteritems(combination["variable_to_value"])
                }
            })
        context_cache.set_cached_value(key, data)
        return data
Exemple #5
0
def get_orderable_variation_children(product, request, variation_variables):
    if not variation_variables:
        variation_variables = product.variation_variables.all().prefetch_related("values")

    key, val = context_cache.get_cached_value(
        identifier="orderable_variation_children", item=product, context=request,
        variation_variables=variation_variables)
    if val is not None:
        return val

    orderable_variation_children = OrderedDict()
    orderable = 0
    for combo_data in product.get_all_available_combinations():
        combo = combo_data["variable_to_value"]
        for k, v in six.iteritems(combo):
            if k not in orderable_variation_children:
                orderable_variation_children[k] = []

        res = ProductVariationResult.resolve(product, combo)
        if res and res.get_shop_instance(request.shop).is_orderable(
                supplier=None,
                customer=request.customer,
                quantity=1
        ):
            orderable += 1

            for k, v in six.iteritems(combo):
                if v not in orderable_variation_children[k]:
                    orderable_variation_children[k].append(v)

    values = (orderable_variation_children, orderable != 0)
    context_cache.set_cached_value(key, values)
    return values
Exemple #6
0
def get_many_cached_price_info(context, item, quantity=1, **context_args):
    """
    Get cached prices info list

    :param object|WSGIRequest context: the context should contain at least a shop and a customer property
    :param object item
    :param float|Decimal quantity
    """
    key, prices_infos = context_cache.get_cached_value(
        many=True,
        **_get_price_info_cache_key_params(context, item, quantity, **context_args)
    )

    if prices_infos:
        try:
            iter(prices_infos)
        except TypeError:
            return None

        from django.utils.timezone import now
        now_timestamp = to_timestamp(now())

        # make sure to check all experiration dates
        for price_info in prices_infos:
            # if one price has expired, we invalidate the entire cache
            if isinstance(price_info, PriceInfo) and price_info.expires_on and price_info.expires_on < now_timestamp:
                return None

    return prices_infos
Exemple #7
0
    def __call__(self, context, item, quantity=1, include_taxes=None, allow_cache=True):
        key, val = context_cache.get_cached_value(
            identifier=self.cache_identifier, item=item, context=context.get('request', context),
            quantity=quantity, include_taxes=include_taxes, name=self.name, allow_cache=allow_cache)
        if val is not None:
            return val

        options = PriceDisplayOptions.from_context(context)
        if options.hide_prices:
            context_cache.set_cached_value(key, "")
            return ""

        if include_taxes is None:
            include_taxes = options.include_taxes

        request = context.get('request')
        orig_priceful = _get_priceful(request, item, quantity)
        if not orig_priceful:
            context_cache.set_cached_value(key, "")
            return ""
        priceful = convert_taxness(request, item, orig_priceful, include_taxes)
        price_value = getattr(priceful, self.property_name)
        val = money(price_value)
        context_cache.set_cached_value(key, val)
        return val
Exemple #8
0
def get_products_for_categories(context, categories, n_products=6, orderable_only=True):
    request = context["request"]
    key, products = context_cache.get_cached_value(
        identifier="products_for_category",
        item=cache_utils.get_products_for_category_cache_item(request.shop),
        context=request,
        n_products=n_products,
        categories=categories,
        orderable_only=orderable_only
    )
    if products is not None:
        return products

    products = _get_listed_products(
        context,
        n_products,
        ordering="?",
        filter_dict={
            "variation_parent": None,
            "shop_products__categories__in": categories
        },
        orderable_only=orderable_only
    )
    products = cache_product_things(request, products)
    context_cache.set_cached_value(key, products, settings.SHUUP_TEMPLATE_HELPERS_CACHE_DURATION)
    return products
    def get_fields(self, request, category=None):
        if not Category.objects.filter(shops=request.shop).exists():
            return

        key, val = context_cache.get_cached_value(
            identifier="categoryproductfilter", item=self, context=request, category=category)
        if val:
            return val

        language = get_language()
        base_queryset = Category.objects.all_visible(request.customer, request.shop, language=language)
        if category:
            q = Q(
                Q(shop_products__categories=category),
                ~Q(shop_products__visibility=ShopProductVisibility.NOT_VISIBLE)
            )
            queryset = base_queryset.filter(q).exclude(pk=category.pk).distinct()
        else:
            # Show only first level when there is no category selected
            queryset = base_queryset.filter(parent=None)

        data = [
            (
                "categories",
                CommaSeparatedListField(
                    required=False,
                    label=get_form_field_label("categories", _('Categories')),
                    widget=FilterWidget(choices=[(cat.pk, cat.name) for cat in queryset])
                )
            )
        ]
        context_cache.set_cached_value(key, data)
        return data
Exemple #10
0
def get_listed_products(context, n_products, ordering=None, filter_dict=None, orderable_only=True, extra_filters=None):
    """
    A cached version of _get_listed_products
    """
    request = context["request"]

    key, products = context_cache.get_cached_value(
        identifier="listed_products",
        item=cache_utils.get_listed_products_cache_item(request.shop),
        context=request,
        n_products=n_products,
        ordering=ordering,
        filter_dict=filter_dict,
        orderable_only=orderable_only,
        extra_filters=hash(str(extra_filters))
    )
    if products is not None:
        return products

    products = _get_listed_products(
        context,
        n_products,
        ordering=ordering,
        filter_dict=filter_dict,
        orderable_only=orderable_only,
        extra_filters=extra_filters
    )
    products = cache_product_things(request, products)
    context_cache.set_cached_value(key, products, settings.SHUUP_TEMPLATE_HELPERS_CACHE_DURATION)
    return products
Exemple #11
0
    def _get_cached_product_price_info(self, shop_product):
        key, val = context_cache.get_cached_value(identifier="shop_product_price_info",
                                                  item=shop_product,
                                                  context={"customer": self.context["customer"]},
                                                  allow_cache=True)
        if val is not None:
            return val

        price_info = self._get_product_price_info(shop_product)
        context_cache.set_cached_value(key, price_info)
        return price_info
Exemple #12
0
    def get_package_content(self, shop_product):
        key, val = context_cache.get_cached_value(identifier="package_contents",
                                                  item=shop_product,
                                                  context={"customer": self.context["customer"]},
                                                  allow_cache=True)
        if val is not None:
            return val

        package_contents = self._get_package_content(shop_product)
        context_cache.set_cached_value(key, package_contents)
        return package_contents
Exemple #13
0
    def get_variations(self, shop_product):
        key, val = context_cache.get_cached_value(identifier="variations",
                                                  item=shop_product,
                                                  context={"customer": self.context["customer"]},
                                                  allow_cache=True)
        if val is not None:
            return val

        variations = self._get_variations(shop_product)
        context_cache.set_cached_value(key, variations)
        return variations
Exemple #14
0
    def get_cross_sell(self, shop_product):
        key, val = context_cache.get_cached_value(identifier="cross_sell",
                                                  item=shop_product,
                                                  context={"customer": self.context["customer"]},
                                                  allow_cache=True)
        if val is not None:
            return val

        cross_sell_data = self._get_cross_sell(shop_product)
        context_cache.set_cached_value(key, cross_sell_data)
        return cross_sell_data
Exemple #15
0
def get_all_manufacturers(context):
    request = context["request"]
    key, manufacturers = context_cache.get_cached_value(
        identifier="all_manufacturers", item=None, context=request)
    if manufacturers is not None:
        return manufacturers

    products = Product.objects.listed(shop=request.shop, customer=request.customer)
    manufacturers_ids = products.values_list("manufacturer__id").distinct()
    manufacturers = Manufacturer.objects.filter(pk__in=manufacturers_ids)
    context_cache.set_cached_value(key, manufacturers, settings.SHUUP_TEMPLATE_HELPERS_CACHE_DURATION)
    return manufacturers
Exemple #16
0
def is_visible(context, product):
    key, val = context_cache.get_cached_value(identifier="is_visible", item=product, context=context)
    if val is not None:
        return val

    request = context["request"]
    shop_product = product.get_shop_instance(shop=request.shop, allow_cache=True)
    for error in shop_product.get_visibility_errors(customer=request.customer):  # pragma: no branch
        context_cache.set_cached_value(key, False)
        return False
    context_cache.set_cached_value(key, True)
    return True
Exemple #17
0
def get_best_selling_products(context, n_products=12, cutoff_days=30, orderable_only=True):
    request = context["request"]

    key, products = context_cache.get_cached_value(
        identifier="best_selling_products", item=None, context=request,
        n_products=n_products, cutoff_days=cutoff_days, orderable_only=orderable_only)
    if products is not None and _can_use_cache(products, request.shop, request.customer):
        return products

    products = _get_best_selling_products(cutoff_days, n_products, orderable_only, request)
    context_cache.set_cached_value(key, products, settings.SHUUP_TEMPLATE_HELPERS_CACHE_DURATION)
    return products
Exemple #18
0
    def get_shop_instance(self, shop, allow_cache=False):
        """
        :type shop: shuup.core.models.Shop
        :rtype: shuup.core.models.ShopProduct
        """
        key, val = context_cache.get_cached_value(
            identifier="shop_product", item=self, context={"shop": shop}, allow_cache=allow_cache)
        if val is not None:
            return val

        shop_inst = self.shop_products.get(shop=shop)
        context_cache.set_cached_value(key, shop_inst)
        return shop_inst
Exemple #19
0
    def __call__(self, context, item, quantity=1, allow_cache=True):
        key, val = context_cache.get_cached_value(
            identifier=self.cache_identifier, item=item, context=context.get('request', context),
            quantity=quantity, name=self.name, allow_cache=allow_cache)
        if val is not None:
            return val

        priceful = _get_priceful(context.get('request'), item, quantity)
        if not priceful:
            context_cache.set_cached_value(key, "")
            return ""

        val = percent(getattr(priceful, self.property_name))
        context_cache.set_cached_value(key, val)
        return val
Exemple #20
0
def get_product_cross_sells(
        context, product, relation_type=ProductCrossSellType.RELATED,
        count=4, orderable_only=True):
    request = context["request"]

    key, products = context_cache.get_cached_value(
        identifier="product_cross_sells",
        item=cache_utils.get_cross_sells_cache_item(request.shop),
        context=request,
        product=product,
        relation_type=relation_type,
        count=count,
        orderable_only=orderable_only
    )

    if products is not None:
        return products

    rtype = map_relation_type(relation_type)
    related_product_ids = list((
        ProductCrossSell.objects
        .filter(product1=product, type=rtype)
        .order_by("weight")[:(count * 4)]).values_list("product2_id", flat=True)
    )

    related_products = []
    for product in Product.objects.filter(id__in=related_product_ids):
        try:
            shop_product = product.get_shop_instance(request.shop, allow_cache=True)
        except ShopProduct.DoesNotExist:
            continue
        if orderable_only:
            for supplier in Supplier.objects.enabled():
                if shop_product.is_orderable(
                        supplier, request.customer, shop_product.minimum_purchase_quantity, allow_cache=True):
                    related_products.append(product)
                    break
        elif shop_product.is_visible(request.customer):
            related_products.append(product)

    # Order related products by weight. Related product ids is in weight order.
    # If same related product is linked twice to product then lowest weight stands.
    related_products.sort(key=lambda prod: list(related_product_ids).index(prod.id))
    products = related_products[:count]
    context_cache.set_cached_value(key, products, settings.SHUUP_TEMPLATE_HELPERS_CACHE_DURATION)
    return products
Exemple #21
0
    def is_orderable(self, supplier, customer, quantity, allow_cache=True):
        key, val = context_cache.get_cached_value(
            identifier="is_orderable", item=self, context={"customer": customer},
            supplier=supplier, quantity=quantity, allow_cache=allow_cache)
        if customer and val is not None:
            return val

        if not supplier:
            supplier = self.suppliers.first()  # TODO: Allow multiple suppliers
        for message in self.get_orderability_errors(supplier=supplier, quantity=quantity, customer=customer):
            if customer:
                context_cache.set_cached_value(key, False)
            return False

        if customer:
            context_cache.set_cached_value(key, True)
        return True
Exemple #22
0
def get_product_queryset(queryset, request, category, data):
    key_data = OrderedDict()
    for k, v in data.items():
        if isinstance(v, list):
            v = "|".join(v)
        key_data[k] = v

    key, val = context_cache.get_cached_value(
        identifier="product_queryset", item=category, allow_cache=True, context=request, data=key_data)
    if val is not None:
        return val

    for extend_obj in _get_active_modifiers(request.shop, category):
        new_queryset = extend_obj.get_queryset(queryset, data)
        if new_queryset is not None:
            queryset = new_queryset
    context_cache.set_cached_value(key, queryset)
    return queryset
Exemple #23
0
def get_orderable_variation_children(product, request, variation_variables, supplier=None):    # noqa (C901)
    if not variation_variables:
        variation_variables = product.variation_variables.all().prefetch_related("values")

    key, val = context_cache.get_cached_value(
        identifier="orderable_variation_children",
        item=product, context=request,
        variation_variables=variation_variables,
        supplier=supplier
    )
    if val is not None:
        return _unpack_orderable_variation_children_from_cache(val)

    orderable_variation_children = OrderedDict()
    orderable = 0

    for combo_data in product.get_all_available_combinations():
        combo = combo_data["variable_to_value"]
        for variable, values in six.iteritems(combo):
            if variable not in orderable_variation_children:
                orderable_variation_children[variable] = []

        res = ProductVariationResult.resolve(product, combo)
        if not res:
            continue

        try:
            shop_product = res.get_shop_instance(request.shop)
        except ShopProduct.DoesNotExist:
            continue

        if res and shop_product.is_orderable(
                supplier=supplier, customer=request.customer, quantity=shop_product.minimum_purchase_quantity):
            orderable += 1
            for variable, value in six.iteritems(combo):
                if value not in orderable_variation_children[variable]:
                    orderable_variation_children[variable].append(value)

    orderable = (orderable > 0)
    values = (orderable_variation_children, orderable)
    context_cache.set_cached_value(key, _pack_orderable_variation_children_to_cache(*values))
    return values
Exemple #24
0
def get_random_products(context, n_products=6, orderable_only=True):
    request = context["request"]
    key, products = context_cache.get_cached_value(
        identifier="random_products", item=None, context=request,
        n_products=n_products, orderable_only=orderable_only)
    if products is not None and _can_use_cache(products, request.shop, request.customer):
        return products

    products = get_listed_products(
        context,
        n_products,
        ordering="?",
        filter_dict={
            "variation_parent": None
        },
        orderable_only=orderable_only,
    )
    products = cache_product_things(request, products)
    context_cache.set_cached_value(key, products, settings.SHUUP_TEMPLATE_HELPERS_CACHE_DURATION)
    return products
Exemple #25
0
def get_cached_price_info(context, item, quantity=1, **context_args):
    """
    Get a cached price info

    :param object|WSGIRequest context: the context should contain at least a shop and a customer property
    :param object item
    :param float|Decimal quantity
    """
    key, price_info = context_cache.get_cached_value(
        **_get_price_info_cache_key_params(context, item, quantity, **context_args)
    )

    from django.utils.timezone import now
    now_ts = to_timestamp(now())

    # price has expired
    if price_info and isinstance(price_info, PriceInfo) and price_info.expires_on and price_info.expires_on < now_ts:
        price_info = None

    return price_info
Exemple #26
0
    def get_cross_sell(self, shop_product):
        key, val = context_cache.get_cached_value(identifier="cross_sell",
                                                  item=shop_product,
                                                  context={"customer": self.context["customer"]},
                                                  allow_cache=True)
        if val is not None:
            return val

        cross_sell_data = {
            "recommended": [],
            "related": [],
            "computed": [],
            "bought_with": []
        }

        keys = {
            ProductCrossSellType.RECOMMENDED: "recommended",
            ProductCrossSellType.RELATED: "related",
            ProductCrossSellType.COMPUTED: "computed",
            ProductCrossSellType.BOUGHT_WITH: "bought_with",
        }
        customer = self.context["customer"]
        for cross_sell in shop_product.product.cross_sell_1.all():
            try:
                cross_shop_product = cross_sell.product2.get_shop_instance(shop_product.shop)
            except ShopProduct.DoesNotExist:
                continue

            supplier = cross_shop_product.suppliers.first()
            quantity = cross_shop_product.minimum_purchase_quantity

            if not cross_shop_product.is_orderable(supplier=supplier, customer=customer, quantity=quantity):
                continue

            key = keys[cross_sell.type]
            cross_sell_data[key].append(self.children_serializer(cross_shop_product, context=self.context).data)

        context_cache.set_cached_value(key, cross_sell_data)
        return cross_sell_data
Exemple #27
0
    def is_orderable(self, supplier, customer, quantity, allow_cache=True):
        """
        Product to be orderable it needs to be visible and purchasable
        """
        key, val = context_cache.get_cached_value(
            identifier="is_orderable", item=self, context={"customer": customer},
            supplier=supplier, stock_managed=bool(supplier and supplier.stock_managed),
            quantity=quantity, allow_cache=allow_cache)
        if customer and val is not None:
            return val

        if not supplier:
            supplier = self.get_supplier(customer, quantity)

        for message in self.get_orderability_errors(supplier=supplier, quantity=quantity, customer=customer):
            if customer:
                context_cache.set_cached_value(key, False)
            return False

        if customer:
            context_cache.set_cached_value(key, True)
        return True
Exemple #28
0
    def is_orderable(self, supplier, customer, quantity, allow_cache=True):
        key, val = context_cache.get_cached_value(
            identifier="is_orderable",
            item=self,
            context={"customer": customer},
            supplier=supplier,
            quantity=quantity,
            allow_cache=allow_cache)
        if customer and val is not None:
            return val

        if not supplier:
            supplier = self.suppliers.first()  # TODO: Allow multiple suppliers
        for message in self.get_orderability_errors(supplier=supplier,
                                                    quantity=quantity,
                                                    customer=customer):
            if customer:
                context_cache.set_cached_value(key, False)
            return False

        if customer:
            context_cache.set_cached_value(key, True)
        return True
Exemple #29
0
def _get_active_modifiers(shop=None, category=None):
    key = None
    if category:
        key, val = context_cache.get_cached_value(
            identifier="active_modifiers", item=category, allow_cache=True, context={"shop": shop})
        if val is not None:
            return val

    configurations = get_configuration(shop=shop, category=category)

    def sorter(extend_obj):
        return extend_obj.get_ordering(configurations)

    objs = []
    for cls in get_provide_objects(FORM_MODIFIER_PROVIDER_KEY):
        obj = cls()
        if obj.should_use(configurations):
            objs.append(obj)

    sorted_objects = sorted(objs, key=sorter)
    if category and key:
        context_cache.set_cached_value(key, sorted_objects)
    return sorted_objects
Exemple #30
0
    def get_package_content(self, shop_product):
        key, val = context_cache.get_cached_value(identifier="package_contents",
                                                  item=shop_product,
                                                  context={"customer": self.context["customer"]},
                                                  allow_cache=True)
        if val is not None:
            return val

        package_contents = []
        pkge_links = ProductPackageLink.objects.filter(parent=shop_product.product)
        for pkge_link in pkge_links:
            try:
                pkge_shop_product = pkge_link.parent.get_shop_instance(shop_product.shop)

                package_contents.append({
                    "quantity": pkge_link.quantity,
                    "product": self.children_serializer(pkge_shop_product, context=self.context).data
                })
            except ShopProduct.DoesNotExist:
                continue

        context_cache.set_cached_value(key, package_contents)
        return package_contents
Exemple #31
0
def get_products_for_categories(context, categories, n_products=6, orderable_only=True):
    request = context["request"]
    key, products = context_cache.get_cached_value(
        identifier="products_for_category",
        item=cache_utils.get_products_for_category_cache_item(request.shop),
        context=request,
        n_products=n_products,
        categories=categories,
        orderable_only=orderable_only,
    )
    if products is not None:
        return products

    products = _get_listed_products(
        context,
        n_products,
        ordering="?",
        filter_dict={"variation_parent": None, "shop_products__categories__in": categories},
        orderable_only=orderable_only,
    )
    products = cache_product_things(request, products)
    context_cache.set_cached_value(key, products, settings.SHUUP_TEMPLATE_HELPERS_CACHE_DURATION)
    return products
def _get_active_modifiers(shop=None, category=None):
    key = None
    if category:
        key, val = context_cache.get_cached_value(
            identifier="active_modifiers", item=category, allow_cache=True, context={"shop": shop})
        if val is not None:
            return val

    configurations = get_configuration(shop=shop, category=category)

    def sorter(extend_obj):
        return extend_obj.get_ordering(configurations)

    objs = []
    for cls in get_provide_objects(FORM_MODIFIER_PROVIDER_KEY):
        obj = cls()
        if obj.should_use(configurations):
            objs.append(obj)

    sorted_objects = sorted(objs, key=sorter)
    if category and key:
        context_cache.set_cached_value(key, sorted_objects)
    return sorted_objects
Exemple #33
0
def get_random_products(context, n_products=6, orderable_only=True):
    request = context["request"]
    key, products = context_cache.get_cached_value(
        identifier="random_products",
        item=None,
        context=request,
        n_products=n_products,
        orderable_only=orderable_only)
    if products is not None and _can_use_cache(products, request.shop,
                                               request.customer):
        return products

    products = get_listed_products(
        context,
        n_products,
        ordering="?",
        filter_dict={"variation_parent": None},
        orderable_only=orderable_only,
    )
    products = cache_product_things(request, products)
    context_cache.set_cached_value(
        key, products, settings.SHUUP_TEMPLATE_HELPERS_CACHE_DURATION)
    return products
Exemple #34
0
def get_cached_price_info(context, item, quantity=1, **context_args):
    """
    Get a cached price info

    :param object|WSGIRequest context: the context should contain at least a shop and a customer property
    :param object item
    :param float|Decimal quantity
    """
    key, price_info = context_cache.get_cached_value(
        **_get_price_info_cache_key_params(context, item, quantity, **
                                           context_args))

    from django.utils.timezone import now

    now_ts = to_timestamp(now())

    # price has expired
    if price_info and isinstance(
            price_info, PriceInfo
    ) and price_info.expires_on and price_info.expires_on < now_ts:
        price_info = None

    return price_info
Exemple #35
0
def get_product_highlight(request, plugin_type, cutoff_days, count,
                          cache_timeout):
    key, html = context_cache.get_cached_value(
        identifier="xtheme_category_proudcts_highlights",
        item=PRODUCT_HIGHLIGHT_CACHE_KEY_PREFIX % {"shop_id": request.shop.pk},
        context=request,
        plugin_type=plugin_type,
        cutoff_days=cutoff_days,
        count=count,
        cache_timeout=cache_timeout)
    if html is not None:
        return HttpResponse(html)

    plugin = ProductHighlightPlugin(
        config={
            "type": plugin_type,
            "cutoff_days": int(cutoff_days),
            "count": int(count),
            "cache_timeout": int(cache_timeout)
        })
    html = plugin.render(dict(request=request))
    context_cache.set_cached_value(key, html, int(cache_timeout))
    return HttpResponse(html)
    def get_fields(self, request, category=None):
        if not Category.objects.exists():
            return

        key, val = context_cache.get_cached_value(
            identifier="categoryproductfilter",
            item=self,
            context=request,
            category=category)
        if val:
            return val

        language = get_language()
        base_queryset = Category.objects.all_visible(request.customer,
                                                     request.shop,
                                                     language=language)
        if category:
            q = Q(
                Q(shop_products__categories=category),
                ~Q(shop_products__visibility=ShopProductVisibility.NOT_VISIBLE)
            )
            queryset = base_queryset.filter(q).exclude(
                pk=category.pk).distinct()
        else:
            # Show only first level when there is no category selected
            queryset = base_queryset.filter(parent=None)

        data = [
            ("categories",
             forms.MultipleChoiceField(
                 choices=[(cat.pk, cat.name) for cat in queryset],
                 required=False,
                 label=get_form_field_label("categories", _('Categories')),
                 widget=FilterWidget())),
        ]
        context_cache.set_cached_value(key, data)
        return data
Exemple #37
0
def get_search_product_ids(request, query, limit=settings.SHUUP_SIMPLE_SEARCH_LIMIT):
    query = query.strip().lower()
    cache_key_elements = {
        "query": query,
        "shop": request.shop.pk,
        "customer": request.customer.pk
    }

    key, val = context_cache.get_cached_value(
        identifier="simple_search", item=None, context=request, cache_key_elements=cache_key_elements)
    if val is not None:
        return val

    product_ids = get_product_ids_for_query_str(request, query, limit)
    for word in query.split(" ") or []:
        if word == query:
            break
        prod_count = len(product_ids)
        if prod_count >= limit:
            break
        product_ids += get_product_ids_for_query_str(request, word.strip(), limit, product_ids)

    context_cache.set_cached_value(key, product_ids[:limit])
    return product_ids
Exemple #38
0
def get_newest_products(context, n_products=6, orderable_only=True):
    request = context["request"]

    key, products = context_cache.get_cached_value(
        identifier="newest_products",
        item=cache_utils.get_newest_products_cache_item(request.shop),
        context=request,
        n_products=n_products, orderable_only=orderable_only
    )
    if products is not None:
        return products

    products = _get_listed_products(
        context,
        n_products,
        ordering="-pk",
        filter_dict={
            "variation_parent": None
        },
        orderable_only=orderable_only
    )
    products = cache_product_things(request, products)
    context_cache.set_cached_value(key, products, settings.SHUUP_TEMPLATE_HELPERS_CACHE_DURATION)
    return products
Exemple #39
0
def get_search_product_ids(request, query, limit=settings.SHUUP_SIMPLE_SEARCH_LIMIT):
    query = query.strip().lower()
    cache_key_elements = {
        "query": query,
        "shop": request.shop.pk,
        "customer": request.customer.pk
    }

    key, val = context_cache.get_cached_value(
        identifier="simple_search", item=None, context=request, cache_key_elements=cache_key_elements)
    if val is not None:
        return val

    product_ids = get_product_ids_for_query_str(request, query, limit)
    for word in query.split(" ") or []:
        if word == query:
            break
        prod_count = len(product_ids)
        if prod_count >= limit:
            break
        product_ids += get_product_ids_for_query_str(request, word.strip(), limit, product_ids)

    context_cache.set_cached_value(key, product_ids[:limit])
    return product_ids
Exemple #40
0
def get_best_selling_products(context,
                              n_products=12,
                              cutoff_days=30,
                              orderable_only=True):
    request = context["request"]

    key, product_ids = context_cache.get_cached_value(
        identifier="best_selling_products",
        item=None,
        context=request,
        n_products=n_products,
        cutoff_days=cutoff_days,
        orderable_only=orderable_only)

    if product_ids is not None and _can_use_cache(product_ids, request.shop,
                                                  request.customer):
        return Product.objects.filter(id__in=product_ids)

    products = _get_best_selling_products(cutoff_days, n_products,
                                          orderable_only, request)
    product_ids = [product.id for product in products]
    context_cache.set_cached_value(
        key, product_ids, settings.SHUUP_TEMPLATE_HELPERS_CACHE_DURATION)
    return products
Exemple #41
0
def get_product_cross_sells(
        context, product, relation_type=ProductCrossSellType.RELATED,
        count=4, orderable_only=True, use_variation_parents=False):
    request = context["request"]

    key, products = context_cache.get_cached_value(
        identifier="product_cross_sells",
        item=cache_utils.get_cross_sells_cache_item(request.shop),
        context=request,
        product=product,
        relation_type=relation_type,
        count=count,
        orderable_only=orderable_only,
        use_variation_parents=use_variation_parents
    )

    if products is not None:
        return products

    rtype = map_relation_type(relation_type)

    # if this product is parent, then use all children instead
    if product.mode in [ProductMode.VARIABLE_VARIATION_PARENT, ProductMode.SIMPLE_VARIATION_PARENT]:
        cross_sell_products = ProductCrossSell.objects.filter(
            product1__in=product.variation_children.all(),
            type=rtype
        )
    else:
        cross_sell_products = ProductCrossSell.objects.filter(product1=product, type=rtype)

    related_product_ids = list(
        cross_sell_products.order_by("weight")[:(count * 4)].values_list("product2_id", flat=True)
    )

    sorted_related_products = []
    for product in Product.objects.filter(id__in=related_product_ids):
        sort_order = related_product_ids.index(product.pk)

        # use the variation parent when configured
        if use_variation_parents and product.variation_parent:
            product = product.variation_parent

        try:
            shop_product = product.get_shop_instance(request.shop, allow_cache=True)
        except ShopProduct.DoesNotExist:
            continue
        if orderable_only:
            for supplier in Supplier.objects.enabled():
                if shop_product.is_orderable(
                        supplier, request.customer, shop_product.minimum_purchase_quantity, allow_cache=True):
                    sorted_related_products.append((sort_order, product))
                    break
        elif shop_product.is_visible(request.customer):
            sorted_related_products.append((sort_order, product))

    # Order related products by weight. Related product ids is in weight order.
    # If same related product is linked twice to product then lowest weight stands.
    sorted_related_products.sort(key=lambda pair: pair[0])
    products = []

    for sort_order, product in sorted_related_products[:count]:
        if product not in products:
            products.append(product)

    context_cache.set_cached_value(key, products, settings.SHUUP_TEMPLATE_HELPERS_CACHE_DURATION)
    return products
Exemple #42
0
def get_price_expiration(context, product):
    """
    Returns the price expiration for the product through a UNIX timestamp

    This routine loads all dates that can possibly affect the price of the product in the future.

    After fetching all the event dates, the expiration time will
        be the minimum datetime that is greater than now:

        expire_on = min(
            event_date for event_dates in [
                next_discount_start,
                next_discount_ends,
                next_happy_hour_start,
                next_happy_hour_end,
                next_availability_exception_start,
                next_availability_exception_end
            ]
            if event_date > now
        )

    :rtype numbers.Number|None
    :returns the price expiration time timestamp
    """
    cache_params = dict(identifier="price_expiration",
                        item=_get_price_expiration_cache_key(context.shop.pk),
                        context={})

    if settings.SHUUP_DISCOUNTS_PER_PRODUCT_EXPIRATION_DATES:
        cache_params["customer"] = getattr(context, "customer", None)
        cache_params["product"] = product

    key, value = context_cache.get_cached_value(**cache_params)
    if value is not None:
        return value

    context_cache_key = "price_expiration_%(shop_id)s" % dict(
        shop_id=context.shop.pk)
    if hasattr(context, "context_cache_key"):
        return getattr(context, context_cache_key)

    from shuup.discounts.models import AvailabilityException, Discount, TimeRange

    if settings.SHUUP_DISCOUNTS_PER_PRODUCT_EXPIRATION_DATES:
        potential_discounts = get_potential_discounts_for_product(
            context, product, available_only=False)
    else:
        potential_discounts = Discount.objects.active(context.shop)

    event_dates = []

    availability_exceptions = AvailabilityException.objects.filter(
        discounts__in=potential_discounts).distinct()
    for start_datetime, end_datetime in availability_exceptions.values_list(
            "start_datetime", "end_datetime"):
        event_dates.extend([start_datetime, end_datetime])

    time_ranges = TimeRange.objects.filter(
        happy_hour__discounts__in=potential_discounts).distinct()
    for weekday, from_hour, to_hour in time_ranges.values_list(
            "weekday", "from_hour", "to_hour"):
        event_dates.extend(
            get_next_dates_for_range(weekday, from_hour, to_hour))

    from django.utils.timezone import now
    now_datetime = now()

    if event_dates:
        min_event_date = (min(event_date for event_date in event_dates
                              if event_date > now_datetime))
        min_event_date_timestamp = to_timestamp(min_event_date)

        # cache the value in the context cache, setting the timeout as the price expiration time
        cache_timeout = max((min_event_date - now_datetime).total_seconds(), 0)
        context_cache.set_cached_value(key,
                                       min_event_date_timestamp,
                                       timeout=cache_timeout)

        # cache the context in the context, so if it is used again it will contain the calculated value
        setattr(context, context_cache_key, min_event_date_timestamp)

        return min_event_date_timestamp