Source code for shuup.core.utils.context_cache

# -*- coding: utf-8 -*-
# This file is part of Shuup.
#
# Copyright (c) 2012-2017, Shoop Commerce Ltd. All rights reserved.
#
# This source code is licensed under the OSL-3.0 license found in the
# LICENSE file in the root directory of this source tree.
import six
from django.core.handlers.wsgi import WSGIRequest
from django.db.models import Q, QuerySet
from parler.managers import TranslatableQuerySet

from shuup.core import cache

try:
    from urllib.parse import urlparse, parse_qs
except ImportError:  # Py2 fallback
    from urlparse import urlparse, parse_qs


HASHABLE_KEYS = ["customer_groups", "customer", "shop"]

GENERIC_CACHE_NAMESPACE_PREFIX = "generic_context_cache"


[docs]def get_cached_value(identifier, item, context, **kwargs): """ Get item from context cache by identifier Accepts optional kwargs parameter `allow_cache` which will skip fetching the actual cached object. When `allow_cache` is set to False only cache key for identifier, item, context combination is returned. :param identifier: Any :type identifier: string :param item: Any :param context: Any :type context: dict :return: Cache key and cached value if allowed :rtype: tuple(str, object) """ allow_cache = True if "allow_cache" in kwargs: allow_cache = kwargs.pop("allow_cache") key = _get_cache_key_for_context(identifier, item, context, **kwargs) if not allow_cache: return key, None return key, cache.get(key)
[docs]def set_cached_value(key, value, timeout=None): """ Set value to context cache :param key: Unique key formed to the context :param value: Value to cache :param timeout: Timeout as seconds :type timeout: int """ cache.set(key, value, timeout=timeout)
[docs]def bump_cache_for_shop_product(shop_product): """ Bump cache for given shop product Clear cache for shop product, product linked to it and all the children. :param shop_product: shop product object :type shop_product: shuup.core.models.ShopProduct """ from shuup.core.models import ShopProduct bump_cache_for_item(shop_product) bump_cache_for_item(shop_product.product) q = Q() q |= Q(product__variation_parent_id=shop_product.product) if shop_product.product.variation_parent: q |= Q(product_id=shop_product.product.variation_parent.id) for child in ShopProduct.objects.filter(q).select_related("product"): bump_cache_for_item(child) bump_cache_for_item(child.product) # Bump all package parents if shop_product.product.is_package_child(): for package_parent in shop_product.product.get_all_package_parents(): for sp in ShopProduct.objects.filter(product_id=package_parent.id): bump_cache_for_item(sp) bump_cache_for_item(sp.product)
[docs]def bump_cache_for_product(product, shop=None): """ Bump cache for product In case shop is not given all the shop products for the product is bumped. :param product: product object :type product: shuup.core.models.Product :param shop: shop object :type shop: shuup.core.models.Shop|None """ if not shop: from shuup.core.models import ShopProduct for sp in ShopProduct.objects.filter(product_id=product.id): bump_cache_for_shop_product(sp) else: shop_product = product.get_shop_instance(shop=shop, allow_cache=False) bump_cache_for_shop_product(shop_product)
[docs]def bump_cache_for_item(item): """ Bump cache for given item Use this only for non product items. For products and shop_products use `bump_cache_for_product` and `bump_cache_for_shop_product` for those. :param item: Cached object """ cache.bump_version(_get_namespace_for_item(item))
[docs]def bump_cache_for_pk(cls, pk): """ Bump cache for given class and pk combination Use this only for non product items. For products and shop_products use `bump_cache_for_product` and `bump_cache_for_shop_product` for those. In case you need to use this to product or shop_product make sure you also bump related objects like in `bump_cache_for_shop_product`. :param cls: Class for cached object :param pk: pk for cached object """ cache.bump_version("%s-%s" % (_get_namespace_prefix(cls), pk))
[docs]def bump_product_signal_handler(sender, instance, **kwargs): """ Signal handler for clearing product cache :param instance: Shuup product :type instance: shuup.core.models.Product """ bump_cache_for_product(instance)
[docs]def bump_shop_product_signal_handler(sender, instance, **kwargs): """ Signal handler for clearing shop product cache :param instance: Shuup shop product :type instance: shuup.core.models.ShopProduct """ bump_cache_for_shop_product(instance)
def _get_cache_key_for_context(identifier, item, context, **kwargs): namespace = _get_namespace_for_item(item) items = _get_items_from_context(context) for k, v in six.iteritems(kwargs): items[k] = _get_val(v) if isinstance(context, WSGIRequest): query_string = urlparse(context.get_full_path()).query for k, v in six.iteritems(parse_qs(query_string)): items[k] = _get_val(v) return "%s:%s_%s" % (namespace, identifier, hash(frozenset(items.items()))) def _get_items_from_context(context): items = {} if hasattr(context, "items"): for k, v in six.iteritems(context): if k in HASHABLE_KEYS: if k == "customer" and hasattr(v, "groups"): v = v.groups.all() k = "customer_groups" items[k] = _get_val(v) else: for key in HASHABLE_KEYS: val = None if hasattr(context, key): if key == "customer": # some context only has customer, transfer this to customer groups val = "|".join(list(map(str, getattr(context, key).groups.all().values_list("pk", flat=True)))) key = "customer_groups" else: val = _get_val(getattr(context, key)) items[key] = val return items def _get_val(v): if isinstance(v, dict): return hash(frozenset(v.items())) if hasattr(v, "pk"): return v.pk if isinstance(v, QuerySet) or isinstance(v, TranslatableQuerySet): return "|".join(list(map(str, v.all().values_list("pk", flat=True)))) if isinstance(v, list): return "|".join(list(map(str, v))) return v def _get_namespace_for_item(item): return "%s-%s" % (_get_namespace_prefix(item), _get_item_id(item)) def _get_namespace_prefix(item): if hasattr(item, "_meta"): model_meta = item._meta return "%s-%s" % (model_meta.app_label, model_meta.model_name) return GENERIC_CACHE_NAMESPACE_PREFIX def _get_item_id(item): if isinstance(item, int): return item item_id = 0 if item: if hasattr(item, "pk"): item_id = item.pk or 0 else: item_id = item.__class__.lower() if callable(item) else 0 return item_id