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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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