Exemplo n.º 1
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
Exemplo 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
Exemplo n.º 3
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
Exemplo n.º 4
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
Exemplo n.º 5
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
Exemplo n.º 6
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
Exemplo n.º 7
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
Exemplo n.º 8
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
Exemplo 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
Exemplo n.º 10
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
Exemplo n.º 11
0
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)
Exemplo 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
Exemplo 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
Exemplo 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
Exemplo n.º 15
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
Exemplo n.º 16
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
Exemplo n.º 17
0
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)
Exemplo 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
Exemplo 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