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, CommaSeparatedListField( required=False, label=capfirst(variation_key), widget=FilterWidget(choices=choices) ) )) context_cache.set_cached_value(key, fields) return fields
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.E-Commerce_TEMPLATE_HELPERS_CACHE_DURATION) return products
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.E-Commerce_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 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_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_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_all_manufacturers(context): request = context["request"] key, manufacturers = context_cache.get_cached_value( identifier="all_manufacturers", item=cache_utils.get_all_manufacturers_cache_item(request.shop), 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.E-Commerce_TEMPLATE_HELPERS_CACHE_DURATION) return manufacturers
def cache_price_info(context, item, quantity, price_info, **context_args): """ Cache a PriceInfo :param context object|WSGIRequest: the context should contain at least a shop and a customer property :param item any :param quantity float|Decimal :param price_info PriceInfo """ # we can just cache PriceInfo instances if isinstance(price_info, PriceInfo): key = context_cache.get_cache_key_for_context( **_get_price_info_cache_key_params(context, item, quantity, **context_args) ) context_cache.set_cached_value(key, price_info)
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=cache_utils.get_best_selling_products_cache_item(request.shop), context=request, n_products=n_products, cutoff_days=cutoff_days, orderable_only=orderable_only ) if products is not None: return products products = _get_best_selling_products(cutoff_days, n_products, orderable_only, request) context_cache.set_cached_value(key, products, settings.E-Commerce_TEMPLATE_HELPERS_CACHE_DURATION) return products
def get_shop_instance(self, shop, allow_cache=False): """ :type shop: E-Commerce.core.models.Shop :rtype: E-Commerce.core.models.ShopProduct """ from E-Commerce.core.utils import context_cache 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_id=shop.id) context_cache.set_cached_value(key, shop_inst) return shop_inst
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 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 _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 cache_many_price_info(context, item, quantity, prices_infos, **context_args): """ Cache a list of PriceInfo :param object|WSGIRequest context: the context should contain at least a shop and a customer property :param object item :param float|Decimal quantity :param iterable[PriceInfo] prices_infos """ # check whether the prices are iterable try: iter(prices_infos) except TypeError: return # all items must be PriceInfo if not all(isinstance(item, PriceInfo) for item in prices_infos): return key = context_cache.get_cache_key_for_context( many=True, **_get_price_info_cache_key_params(context, item, quantity, **context_args) ) context_cache.set_cached_value(key, prices_infos)
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]: # Remember to exclude relations with the same parent cross_sell_products = ProductCrossSell.objects.filter( product1__in=product.variation_children.visible(request.shop, customer=request.customer), type=rtype ).exclude(product2__in=product.variation_children.visible(request.shop, customer=request.customer)) 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.E-Commerce_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.E-Commerce_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 E-Commerce.discounts.models import AvailabilityException, Discount, TimeRange if settings.E-Commerce_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