def create(self, request, *args, **kwargs): basket = self.get_basket() serializer = self.get_serializer_class()(data=request.data) serializer.is_valid(raise_exception=True) try: product = serializer.validated_data['product'] product.get_shop_instance(shop=self.request.shop) except ObjectDoesNotExist: return ValidationError({ 'code': 'product_not_available', 'error': [_('The requested product does not exists or is not available in the current store')] }) try: handle_add(PricingContext(self.request.shop, basket.customer), basket, product.id, serializer.validated_data['quantity']) basket.save() except ShopMismatchBasketCompatibilityError: raise ValidationError({ 'code': 'shop_mismatch', 'error': _('The requested belongs to another shop') }) # trigger reload, otherwise we have a cached version new_basket = self.get_basket() return Response(APIBasketSerializer(new_basket, context={'request': self.request}).data)
def _get_best_selling_products(cutoff_days, n_products, orderable_only, request, sale_items_only): # noqa (C901) 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 # get all the product ids product_ids = [ d[0] for d in sorted(six.iteritems(combined_variation_products), key=lambda i: i[1], reverse=True) ] products = [] suppliers = [] if orderable_only: # get suppliers for later use suppliers = Supplier.objects.enabled().filter(shops__in=[request.shop]) if sale_items_only: from shuup.core.pricing import PricingContext pricing_context = PricingContext(shop=request.shop, customer=request.customer) # group product ids in groups of n_products # to prevent querying ALL products at once for grouped_product_ids in _group_list_items(product_ids, n_products): for product in Product.objects.filter(id__in=grouped_product_ids): if len(products) == n_products: break if sale_items_only and not _is_sale_item(product, pricing_context): continue try: shop_product = product.get_shop_instance(request.shop, allow_cache=True) except ShopProduct.DoesNotExist: continue 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) if len(products) == n_products: break products = cache_product_things(request, products) products = sorted(products, key=lambda p: product_ids.index(p.id)) # pragma: no branch return products
def update_line(self, data_line, **kwargs): line = BasketLine.from_dict(self, data_line) new_quantity = kwargs.pop("quantity", None) if new_quantity is not None: line.set_quantity(new_quantity) line.update(**kwargs) line.cache_info(PricingContext(self.shop, self.customer)) self._add_or_replace_line(line) return line
def _assert_price(product, shop, expected_price, expected_base_price, customer=None): context = PricingContext(shop=shop, customer=customer or AnonymousContact()) price = product.get_price_info(context) assert price.price.value == expected_price assert price.base_price.value == expected_base_price
def apply_for_basket(self, order_source): from shuup.campaigns.models import CatalogCampaign discounted_base_amount = order_source.total_price_of_products context = PricingContext(order_source.shop, order_source.customer) for line in order_source.get_product_lines(): product = line.product if CatalogCampaign.get_matching( context, product.get_shop_instance(order_source.shop)): discounted_base_amount -= line.price return (discounted_base_amount * self.value)
def matches(self, basket, lines): from shuup.campaigns.models import CatalogCampaign total_undiscounted_price_value = basket.total_price_of_products.value shop = basket.shop context = PricingContext(shop, basket.customer) for line in basket.get_product_lines(): if CatalogCampaign.get_matching( context, line.product.get_shop_instance(shop)): total_undiscounted_price_value -= line.price.value return (total_undiscounted_price_value >= self.amount_value)
def apply_for_basket(self, order_source): from shuup.campaigns.models import CatalogCampaign campaign = self.campaign supplier = campaign.supplier if hasattr(campaign, "supplier") and campaign.supplier else None discounted_base_amount = get_total_price_of_products(order_source, campaign) context = PricingContext(order_source.shop, order_source.customer) for line in order_source.get_product_lines(): if supplier and line.supplier != supplier: continue product = line.product if CatalogCampaign.get_matching(context, product.get_shop_instance(order_source.shop)): discounted_base_amount -= line.price return discounted_base_amount * self.value
def matches(self, basket, lines): from shuup.campaigns.models import CatalogCampaign campaign = self.campaign.first() total_of_products = get_total_price_of_products(basket, campaign) product_lines = basket.get_product_lines() if hasattr(campaign, "supplier") and campaign.supplier: product_lines = [ line for line in product_lines if line.supplier == campaign.supplier ] total_undiscounted_price_value = total_of_products.value shop = basket.shop context = PricingContext(shop, basket.customer) for line in product_lines: if CatalogCampaign.get_matching( context, line.product.get_shop_instance(shop)): total_undiscounted_price_value -= line.price.value return (total_undiscounted_price_value >= self.amount_value)
def get_listed_products(context, n_products, ordering=None, filter_dict=None, orderable_only=True, sale_items_only=False): """ 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 sale_items_only: from shuup.core.pricing import PricingContext pricing_context = PricingContext(shop=shop, customer=customer) if orderable_only: suppliers = Supplier.objects.filter(shops=shop) products = [] for product in products_qs.iterator(): if len(products) == n_products: break try: shop_product = product.get_shop_instance(shop, allow_cache=True) except ShopProduct.DoesNotExist: continue for supplier in suppliers: if shop_product.is_orderable( supplier, customer, shop_product.minimum_purchase_quantity): if sale_items_only and not _is_sale_item( product, pricing_context): continue products.append(product) break return products elif sale_items_only: products = [] for product in products_qs.iterator(): if len(products) == n_products: break if _is_sale_item(product, pricing_context): products.append(product) return products return products_qs[:n_products]
def index_shop_product_price( shop_product: ShopProduct, supplier: Supplier, contact_groups_ids: Iterable[int] = [], ): """ Index discounts for the shop product """ from shuup.discounts.models import ShopProductCatalogDiscountsLink from shuup.discounts.modules import ProductDiscountModule default_price = shop_product.default_price_value context = PricingContext(shop=shop_product.shop, customer=AnonymousContact(), supplier=supplier) discounts = get_potential_discounts_for_product( context, shop_product.product, available_only=False, groups_ids=contact_groups_ids, all_contacts=True) # link this shop product to the potencial discounts discounts_link = ShopProductCatalogDiscountsLink.objects.get_or_create( shop_product=shop_product)[0] discounts_link.discounts.set(discounts) if not discounts.exists(): # delete all discounted prices ProductCatalogDiscountedPrice.objects.filter( product=shop_product.product, shop=shop_product.shop, catalog_rule__module_identifier=ProductDiscountModule.identifier, ).delete() for discount in discounts: discount_options = [default_price] if discount.discounted_price_value is not None: discount_options.append(discount.discounted_price_value) if discount.discount_amount_value is not None: discount_options.append(default_price - discount.discount_amount_value) if discount.discount_percentage is not None: discount_options.append(default_price - (default_price * discount.discount_percentage)) best_discounted_price = max(min(discount_options), 0) happy_hours_times = list( discount.happy_hours.values_list( "time_ranges__from_hour", "time_ranges__to_hour", "time_ranges__weekday", )) # if ther is no happy hour configured, # let's create one rule without time constraints if not happy_hours_times: happy_hours_times.append((None, None, None)) for from_hour, to_hour, weekday in happy_hours_times: catalog_rule = ProductCatalogDiscountedPriceRule.objects.get_or_create( module_identifier=ProductDiscountModule.identifier, contact_group=discount.contact_group, contact=discount.contact, valid_start_date=discount.start_datetime, valid_end_date=discount.end_datetime, valid_start_hour=from_hour, valid_end_hour=to_hour, valid_weekday=weekday, )[0] ProductCatalogDiscountedPrice.objects.update_or_create( product=shop_product.product, shop=shop_product.shop, supplier=supplier, catalog_rule=catalog_rule, defaults=dict(discounted_price_value=best_discounted_price), )
def get_lines(self, basket): for line in basket.get_lines(): line.cache_info(PricingContext(basket.shop, basket.customer)) serializer = APIBasketLineSerializer( line, context={'request': self.context['request']}) yield serializer.data