# -*- coding: utf-8 -*-
# This file is part of Shuup.
#
# Copyright (c) 2012-2021, Shuup Commerce Inc. 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 collections import OrderedDict
from django.http import Http404
from django.utils.safestring import mark_safe
from django.utils.translation import get_language
from shuup.apps.provides import get_provide_objects
from shuup.core.models import (
AttributeVisibility,
Product,
ProductMode,
ProductVariationVariable,
ProductVariationVariableValue,
ShopProduct,
)
from shuup.core.utils import context_cache
from shuup.front.utils.views import cache_product_things
from shuup.utils.importing import cached_load
from shuup.utils.iterables import first
from shuup.utils.numbers import get_string_sort_order
[docs]def get_product_context(request, product, language=None, supplier=None):
return cached_load("SHUUP_FRONT_PRODUCT_CONTEXT_SPEC")(request, product, language, supplier)
[docs]def get_default_product_context(request, product, language=None, supplier=None): # noqa (C901)
"""
Get product context.
Used in `shuup.front.views.product:ProductDetailView`.
:return: A context dict containing everything needed to render product view.
:rtype: dict
"""
if not language:
language = get_language()
shop_product = product.get_shop_instance(request.shop)
context = {}
context["product"] = product
context["category"] = shop_product.primary_category
context["orderability_errors"] = list(
shop_product.get_orderability_errors(
supplier=supplier, quantity=1, customer=request.customer, ignore_minimum=True
)
)
context["variation_children"] = []
selected_variation = None
variation_sku = request.GET.get("variation")
if variation_sku:
try:
selected_variation = product.variation_children.get(sku=variation_sku)
context["selected_variation"] = selected_variation
except Product.DoesNotExist:
raise Http404
if product.mode == ProductMode.SIMPLE_VARIATION_PARENT:
context["variation_children"] = cache_product_things(
request,
sorted(
product.variation_children.language(language).visible(shop=request.shop, customer=request.customer),
key=lambda p: get_string_sort_order(p.variation_name or p.name),
),
)
context["orderable_variation_children"] = []
for p in context["variation_children"]:
try:
if p.get_shop_instance(request.shop).is_orderable(
supplier=supplier, customer=request.customer, quantity=1
):
context["orderable_variation_children"].append(p)
except ShopProduct.DoesNotExist:
pass
elif product.mode == ProductMode.VARIABLE_VARIATION_PARENT:
variation_variables = product.variation_variables.all().prefetch_related("values")
orderable_children, is_orderable = get_orderable_variation_children(
product, request, variation_variables, supplier
)
context["orderable_variation_children"] = orderable_children
context["variation_orderable"] = is_orderable
context["variation_variables"] = variation_variables
if selected_variation:
for combination in product.get_all_available_combinations():
if combination["result_product_pk"] == selected_variation.pk:
values = [v.pk for v in combination["variable_to_value"].values()]
context["selected_variation_values"] = values
break
elif product.is_container():
children = product.get_all_package_children().translated().order_by("translations__name")
context["package_children"] = cache_product_things(request, children)
context["shop_product"] = shop_product
context["attributes"] = product.attributes.filter(
attribute__visibility_mode=AttributeVisibility.SHOW_ON_PRODUCT_PAGE
)
context["primary_image"] = shop_product.public_primary_image
context["images"] = shop_product.public_images
context["supplier"] = supplier
context["order_form"] = _get_order_form(request, context, product, language)
for provide_object in get_provide_objects("product_context_extra"):
provider = provide_object(request, product, language)
if provider.provides_extra_context():
context.update(provider.extra_context)
return context
def _get_order_form(request, context, product, language):
order_forms = sorted(get_provide_objects("front_product_order_form"), key=lambda form: form.priority, reverse=True)
for obj in order_forms:
product_order_form = obj(request, context, product, language)
if product_order_form.is_compatible():
return mark_safe(product_order_form.render())
return None
def _pack_orderable_variation_children_to_cache(orderable_variation_children, orderable):
orderable_variation_children_ids = OrderedDict()
for variable, values in orderable_variation_children.items():
orderable_variation_children_ids[variable.id] = tuple(value.id for value in values)
return (orderable_variation_children_ids, orderable)
def _unpack_orderable_variation_children_from_cache(cached_value):
orderable_variation_children_ids, orderable = cached_value
orderable_variation_children = OrderedDict()
# transform IDs into objects
for variable_id, values_ids in orderable_variation_children_ids.items():
variable = ProductVariationVariable.objects.get(id=variable_id)
values = tuple(ProductVariationVariableValue.objects.filter(id__in=values_ids))
orderable_variation_children[variable] = values
return (orderable_variation_children, orderable)
[docs]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
shop = request.shop
product_queryset = product.variation_children.visible(shop=shop, customer=request.customer).values_list(
"pk", flat=True
)
all_combinations = list(product.get_all_available_combinations())
for shop_product in (
ShopProduct.objects.filter(shop=shop, product__id__in=product_queryset)
.select_related("product")
.prefetch_related("suppliers")
):
shop_product.shop = shop # To avoid query on orderability checks
combo_data = first(combo for combo in all_combinations if combo["result_product_pk"] == shop_product.product.id)
if not combo_data:
continue
combo = combo_data["variable_to_value"]
for variable, values in six.iteritems(combo):
if variable not in orderable_variation_children:
orderable_variation_children[variable] = []
if 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