Source code for shuup.core.utils.price_display

"""
Utilities for displaying prices correctly.

Contents:

  * Class `PriceDisplayOptions` for storing the display options.

  * Helper function `render_price_property` for rendering prices
    correctly from Python code.

  * Various filter classes for implementing Jinja2 filters.
"""

import django_jinja.library

from shuup.compat import contextfilter, contextfunction
from shuup.core.catalog import ProductCatalog, ProductCatalogContext
from shuup.core.pricing import PriceDisplayOptions, Priceful, PriceInfo
from shuup.core.templatetags.shuup_common import money, percent
from shuup.core.utils.price_cache import (
    cache_many_price_info,
    cache_price_info,
    get_cached_price_info,
    get_many_cached_price_info,
)
from shuup.core.utils.prices import convert_taxness

PRICED_CHILDREN_CACHE_KEY = "%s-%s_priced_children"


[docs] def render_price_property(request, item, priceful, property_name="price"): """ Render price property of a Priceful object. :type request: django.http.HttpRequest :type item: shuup.core.taxing.TaxableItem :type priceful: shuup.core.pricing.Priceful :type propert_name: str :rtype: str """ options = PriceDisplayOptions.from_context({"request": request}) if options.hide_prices: return "" new_priceful = convert_taxness(request, item, priceful, options.include_taxes) price_value = getattr(new_priceful, property_name) return money(price_value)
class _ContextObject: def __init__(self, name, property_name=None): self.name = name self.property_name = property_name or name self._register() class _ContextFilter(_ContextObject): def _register(self): django_jinja.library.filter(name=self.name, fn=contextfilter(self)) @property def cache_identifier(self): return f"price_filter_{self.name}" class _ContextFunction(_ContextObject): def _register(self): django_jinja.library.global_function(name=self.name, fn=contextfunction(self)) def _get_item_price_info(request, item, quantity, supplier=None): # the item has a catalog price annotate, use it # this speeds up when using querysets coming from ProductCatalog if hasattr(item, "catalog_price") and hasattr(item, "catalog_discounted_price") and item.catalog_price is not None: shop = request.shop discounted_price = item.catalog_discounted_price if item.catalog_discounted_price is not None else None return PriceInfo( price=shop.create_price((discounted_price if discounted_price else item.catalog_price) * quantity), base_price=shop.create_price(item.catalog_price * quantity), quantity=quantity, ) return _get_priceful(request, item, quantity, supplier)
[docs] class PriceDisplayFilter(_ContextFilter): def __call__( self, context, item, quantity=1, include_taxes=None, allow_cache=True, supplier=None, ): options = PriceDisplayOptions.from_context(context) if options.hide_prices: return "" if include_taxes is None: include_taxes = options.include_taxes request = context.get("request") price_info = ( get_cached_price_info(request, item, quantity, include_taxes=include_taxes, supplier=supplier) if allow_cache else None ) if not price_info: price_info = _get_item_price_info(request, item, quantity, supplier) if not price_info: return "" price_info = convert_taxness(request, item, price_info, include_taxes) if allow_cache: cache_price_info( request, item, quantity, price_info, include_taxes=include_taxes, supplier=supplier, ) return money(getattr(price_info, self.property_name))
[docs] class PricePropertyFilter(_ContextFilter): def __call__(self, context, item, quantity=1, allow_cache=True, supplier=None): request = context.get("request") price_info = get_cached_price_info(request, item, quantity, supplier=supplier) if allow_cache else None if not price_info: price_info = _get_item_price_info(request, item, quantity, supplier) if not price_info: return "" if allow_cache: cache_price_info(request, item, quantity, price_info, supplier=supplier) return getattr(price_info, self.property_name)
[docs] class PricePercentPropertyFilter(_ContextFilter): def __call__(self, context, item, quantity=1, allow_cache=True, supplier=None): request = context.get("request") price_info = get_cached_price_info(request, item, quantity, supplier=supplier) if allow_cache else None if not price_info: price_info = _get_item_price_info(request, item, quantity, supplier) if not price_info: return "" if allow_cache: cache_price_info(request, item, quantity, price_info, supplier=supplier) return percent(getattr(price_info, self.property_name))
[docs] class TotalPriceDisplayFilter(_ContextFilter): def __call__(self, context, source, include_taxes=None): """ :type source: shuup.core.order_creator.OrderSource| shuup.core.models.Order """ options = PriceDisplayOptions.from_context(context) if options.hide_prices: return "" if include_taxes is None: include_taxes = options.include_taxes try: if include_taxes is None: total = source.total_price elif include_taxes: total = source.taxful_total_price else: total = source.taxless_total_price except TypeError: total = source.total_price return money(total)
[docs] def get_priced_children_for_price_range(request, product, quantity, supplier): catalog = ProductCatalog( ProductCatalogContext( shop=request.shop, user=getattr(request, "user", None), supplier=supplier, contact=getattr(request, "customer", None), purchasable_only=True, ) ) product_queryset = ( catalog.get_products_queryset() .filter(pk__in=product.variation_children.values_list("pk", flat=True)) .order_by("catalog_price") ) low = product_queryset.first() if low is None: return [] high = product_queryset.last() return [ (low, _get_item_price_info(request, low, quantity, supplier)), (high, _get_item_price_info(request, high, quantity, supplier)), ]
[docs] class PriceRangeDisplayFilter(_ContextFilter): def __call__(self, context, product, quantity=1, allow_cache=True, supplier=None): """ :type product: shuup.core.models.Product """ options = PriceDisplayOptions.from_context(context) if options.hide_prices: return ("", "") request = context.get("request") priced_products = ( get_many_cached_price_info( request, product, quantity, include_taxes=options.include_taxes, supplier=supplier, ) if allow_cache else None ) if priced_products is None: priced_children_key = PRICED_CHILDREN_CACHE_KEY % (product.id, quantity) priced_products = [] if hasattr(request, priced_children_key): priced_children = getattr(request, priced_children_key) else: priced_children = get_priced_children_for_price_range(request, product, quantity, supplier) or [ ( product, _get_item_price_info(request, product, quantity, supplier), ) ] setattr(request, priced_children_key, priced_children) for child_product, price_info in priced_children: if not price_info: continue priceful = convert_taxness(request, child_product, price_info, options.include_taxes) priced_products.append(priceful) if priced_products and allow_cache: cache_many_price_info( request, product, quantity, priced_products, include_taxes=options.include_taxes, supplier=supplier, ) if not priced_products: return ("", "") return (money(priced_products[0].price), money(priced_products[-1].price))
def _get_priceful(request, item, quantity, supplier): """ Get priceful from given item. If item has `get_price_info` method, it will be called with given `request` and `quantity` as arguments, otherwise the item itself should implement the `Priceful` interface. :type request: django.http.HttpRequest :param request: used as pricing context :type item: shuup.core.taxing.TaxableItem :type quantity: numbers.Number :type supplier: shuup.core.models.Supplier :param supplier: used to pass for pricing context :rtype: shuup.core.pricing.Priceful|None """ if supplier: # Passed from template and sometimes chosen by end user, # but most of the time just decided by supplier strategy. request.supplier = supplier if hasattr(item, "supplier"): # When item already has supplier fe. order and basket lines. # This is always forced and supplier passed from template # can't override this. Though developer should never pass # supplier to template filter while getting price for source line. request.supplier = item.supplier if hasattr(item, "get_price_info"): key_prefix = f"{item.id}-{quantity}-" if supplier: key_prefix += f"-{supplier.id}" price_key = f"{key_prefix}_get_priceful" if hasattr(request, price_key): return getattr(request, price_key) price = item.get_price_info(request, quantity=quantity) setattr(request, price_key, price) return price if hasattr(item, "get_total_cost"): return item.get_total_cost(request.basket) assert isinstance(item, Priceful) return item