Esempio n. 1
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
Esempio n. 2
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.E-Commerce_TEMPLATE_HELPERS_CACHE_DURATION)
    return products
Esempio n. 3
0
    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
Esempio n. 4
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.E-Commerce_TEMPLATE_HELPERS_CACHE_DURATION)
    return products
Esempio n. 5
0
    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
Esempio n. 6
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
Esempio n. 7
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
Esempio n. 8
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
Esempio n. 9
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
Esempio n. 10
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
Esempio n. 11
0
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
Esempio n. 12
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=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
Esempio n. 13
0
    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
Esempio n. 14
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
Esempio n. 15
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
Esempio n. 16
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
Esempio n. 17
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
Esempio n. 18
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]:
        # 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
Esempio n. 19
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.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