# -*- 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.
from collections import defaultdict
import six
from django.conf import settings
from django.core.paginator import Paginator
from django.utils.translation import get_language, get_language_info, ugettext
from jinja2.utils import contextfunction
from mptt.templatetags.mptt_tags import cache_tree_children
from shuup.core.models import (
Category, Manufacturer, Product, ShopProduct, Supplier
)
from shuup.core.utils import context_cache
from shuup.front.utils.product_statistics import get_best_selling_product_info
from shuup.front.utils.views import cache_product_things
from shuup.utils.translation import cache_translations_for_tree
@contextfunction
[docs]def get_listed_products(context, n_products, ordering=None, filter_dict=None, orderable_only=True):
"""
Returns all products marked as listed that are determined to be
visible based on the current context.
:param context: Rendering context
:type context: jinja2.runtime.Context
:param n_products: Number of products to return
:type n_products: int
:param ordering: String specifying ordering
:type ordering: str
:param filter_dict: Dictionary of filter parameters
:type filter_dict: dict[str, object]
:param orderable_only: Boolean limiting results to orderable products
:type orderable_only: bool
:rtype: list[shuup.core.models.Product]
"""
request = context["request"]
customer = request.customer
shop = request.shop
# Todo: Check if this should be cached
if not filter_dict:
filter_dict = {}
products_qs = Product.objects.listed(
shop=shop,
customer=customer,
language=get_language(),
).filter(**filter_dict)
if ordering:
products_qs = products_qs.order_by(ordering)
if orderable_only:
suppliers = Supplier.objects.all()
products = []
for product in products_qs[:(n_products * 4)]:
if len(products) == n_products:
break
shop_product = product.get_shop_instance(shop, allow_cache=True)
for supplier in suppliers:
if shop_product.is_orderable(supplier, customer, shop_product.minimum_purchase_quantity):
products.append(product)
break
return products
products = products_qs[:n_products]
return products
def _can_use_cache(products, shop, customer):
"""
Check whether the cached products can be still used
If any of the products is no more orderable refetch the products
"""
product_ids = [prod.id for prod in products]
for supplier in Supplier.objects.all():
for sp in ShopProduct.objects.filter(product__id__in=product_ids):
if not sp.is_orderable(supplier, customer=customer, quantity=sp.minimum_purchase_quantity):
return False
return True
@contextfunction
[docs]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=None, context=request,
n_products=n_products, cutoff_days=cutoff_days, orderable_only=orderable_only)
if products is not None and _can_use_cache(products, request.shop, request.customer):
return products
products = _get_best_selling_products(cutoff_days, n_products, orderable_only, request)
context_cache.set_cached_value(key, products, settings.SHUUP_TEMPLATE_HELPERS_CACHE_DURATION)
return products
def _get_best_selling_products(cutoff_days, n_products, orderable_only, request):
data = get_best_selling_product_info(
shop_ids=[request.shop.pk],
cutoff_days=cutoff_days
)
combined_variation_products = defaultdict(int)
for product_id, parent_id, qty in data:
if parent_id:
combined_variation_products[parent_id] += qty
else:
combined_variation_products[product_id] += qty
product_ids = [
d[0] for
d in sorted(six.iteritems(combined_variation_products), key=lambda i: i[1], reverse=True)
][:n_products]
products = []
if orderable_only:
# get suppliers for later use
suppliers = Supplier.objects.all()
for product in Product.objects.filter(id__in=product_ids):
shop_product = product.get_shop_instance(request.shop, allow_cache=True)
if orderable_only:
for supplier in suppliers:
if shop_product.is_orderable(supplier, request.customer, shop_product.minimum_purchase_quantity):
products.append(product)
break
elif shop_product.is_visible(request.customer):
products.append(product)
products = cache_product_things(request, products)
products = sorted(products, key=lambda p: product_ids.index(p.id)) # pragma: no branch
return products
@contextfunction
[docs]def get_newest_products(context, n_products=6, orderable_only=True):
request = context["request"]
key, products = context_cache.get_cached_value(
identifier="newest_products", item=None, context=request,
n_products=n_products, orderable_only=orderable_only)
if products is not None and _can_use_cache(products, request.shop, request.customer):
return products
products = get_listed_products(
context,
n_products,
ordering="-pk",
filter_dict={
"variation_parent": None
},
orderable_only=orderable_only,
)
products = cache_product_things(request, products)
context_cache.set_cached_value(key, products, settings.SHUUP_TEMPLATE_HELPERS_CACHE_DURATION)
return products
@contextfunction
[docs]def get_random_products(context, n_products=6, orderable_only=True):
request = context["request"]
key, products = context_cache.get_cached_value(
identifier="random_products", item=None, context=request,
n_products=n_products, orderable_only=orderable_only)
if products is not None and _can_use_cache(products, request.shop, request.customer):
return products
products = get_listed_products(
context,
n_products,
ordering="?",
filter_dict={
"variation_parent": None
},
orderable_only=orderable_only,
)
products = cache_product_things(request, products)
context_cache.set_cached_value(key, products, settings.SHUUP_TEMPLATE_HELPERS_CACHE_DURATION)
return products
@contextfunction
[docs]def get_products_for_category(context, category, n_products=6, orderable_only=True):
request = context["request"]
key, products = context_cache.get_cached_value(
identifier="products_for_category", item=None, context=request,
n_products=n_products, category=category, orderable_only=orderable_only)
if products is not None and _can_use_cache(products, request.shop, request.customer):
return products
products = get_listed_products(
context,
n_products,
ordering="?",
filter_dict={
"variation_parent": None,
"shop_products__categories__in": category
},
orderable_only=orderable_only,
)
products = cache_product_things(request, products)
context_cache.set_cached_value(key, products, settings.SHUUP_TEMPLATE_HELPERS_CACHE_DURATION)
return products
@contextfunction
[docs]def get_all_manufacturers(context):
request = context["request"]
key, manufacturers = context_cache.get_cached_value(
identifier="all_manufacturers", item=None, 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.SHUUP_TEMPLATE_HELPERS_CACHE_DURATION)
return manufacturers
@contextfunction
[docs]def get_root_categories(context):
request = context["request"]
language = get_language()
roots = cache_tree_children(
Category.objects.all_visible(
customer=request.customer, shop=request.shop, language=language))
cache_translations_for_tree(roots, languages=[language])
return roots
@contextfunction
def _get_page_range(current_page, num_pages, range_gap=5):
"""
Get page range around given page for a given number of pages.
>>> list(_get_page_range(1, 10))
[1, 2, 3, 4, 5]
>>> list(_get_page_range(3, 10))
[1, 2, 3, 4, 5]
>>> list(_get_page_range(4, 10))
[2, 3, 4, 5, 6]
>>> list(_get_page_range(7, 10))
[5, 6, 7, 8, 9]
>>> list(_get_page_range(10, 10))
[6, 7, 8, 9, 10]
>>> list(_get_page_range(1, 1))
[1]
>>> list(_get_page_range(1, 4))
[1, 2, 3, 4]
>>> list(_get_page_range(3, 4))
[1, 2, 3, 4]
>>> list(_get_page_range(4, 4))
[1, 2, 3, 4]
"""
assert isinstance(num_pages, int)
assert isinstance(current_page, int)
assert num_pages >= 1
assert current_page >= 1
assert current_page <= num_pages
max_start = max(num_pages - range_gap + 1, 1)
start = min(max(current_page - (range_gap // 2), 1), max_start)
end = min(start + range_gap - 1, num_pages)
return six.moves.range(start, end + 1)
@contextfunction
[docs]def get_shop_language_choices(context):
languages = []
for code, name in settings.LANGUAGES:
lang_info = get_language_info(code)
name_in_current_lang = ugettext(name)
local_name = lang_info["name_local"]
languages.append((code, name_in_current_lang, local_name))
return languages