Repository: saleor/saleor

Issue:
Hide shipping calculation in cart when shipping is not required
I think we should hide "CALCULATE SHIPPING COST" section in cart, when products that are currently in cart don't require shipping. It should be only displayed when there is at least one item that does require shipping.


Discussion:

Resolving patch:
diff --git a/saleor/cart/utils.py b/saleor/cart/utils.py
--- a/saleor/cart/utils.py
+++ b/saleor/cart/utils.py
@@ -8,10 +8,12 @@
 from django.db import transaction
 from django.utils.timezone import now
 from django.utils.translation import pgettext_lazy
+from prices import PriceRange
 from satchless.item import InsufficientStock
 
 from . import CartStatus
 from .models import Cart
+from ..core.utils import to_local_currency
 
 COOKIE_NAME = 'cart'
 
@@ -212,3 +214,27 @@ def func(request, *args, **kwargs):
             return view(request, cart, *args, **kwargs)
         return func
     return get_cart
+
+
+def get_cart_data(cart, shipping_range, currency, discounts):
+    cart_total = None
+    local_cart_total = None
+    shipping_required = False
+    total_with_shipping = None
+    local_total_with_shipping = None
+    if cart:
+        cart_total = cart.get_total(discounts=discounts)
+        local_cart_total = to_local_currency(cart_total, currency)
+        shipping_required = cart.is_shipping_required()
+        total_with_shipping = PriceRange(cart_total)
+        if shipping_required and shipping_range:
+            total_with_shipping = shipping_range + cart_total
+        local_total_with_shipping = to_local_currency(
+            total_with_shipping, currency)
+
+    return {
+        'cart_total': cart_total,
+        'local_cart_total': local_cart_total,
+        'shipping_required': shipping_required,
+        'total_with_shipping': total_with_shipping,
+        'local_total_with_shipping': local_total_with_shipping}
diff --git a/saleor/cart/views.py b/saleor/cart/views.py
--- a/saleor/cart/views.py
+++ b/saleor/cart/views.py
@@ -11,7 +11,8 @@
 from ..shipping.utils import get_shipment_options
 from .forms import ReplaceCartLineForm, CountryForm
 from .models import Cart
-from .utils import check_product_availability_and_warn, get_or_empty_db_cart
+from .utils import (check_product_availability_and_warn, get_or_empty_db_cart,
+                    get_cart_data)
 
 
 @get_or_empty_db_cart(cart_queryset=Cart.objects.for_display())
@@ -30,31 +31,37 @@ def index(request, cart):
             'get_total': line.get_total(discounts=discounts),
             'form': form})
 
-    cart_total = None
-    local_cart_total = None
-    if cart:
-        cart_total = cart.get_total(discounts=discounts)
-        local_cart_total = to_local_currency(cart_total, request.currency)
-
     default_country = get_user_shipping_country(request)
     country_form = CountryForm(initial={'country': default_country})
     default_country_options = get_shipment_options(default_country)
 
+    cart_data = get_cart_data(
+        cart, default_country_options, request.currency, request.discounts)
+    ctx = {
+        'cart_lines': cart_lines,
+        'country_form': country_form,
+        'default_country_options': default_country_options}
+    ctx.update(cart_data)
+
     return TemplateResponse(
-        request, 'cart/index.html',
-        {
-            'cart_lines': cart_lines,
-            'cart_total': cart_total,
-            'local_cart_total': local_cart_total,
-            'country_form': country_form,
-            'default_country_options': default_country_options})
+        request, 'cart/index.html', ctx)
 
 
-def get_shipping_options(request):
+@get_or_empty_db_cart(cart_queryset=Cart.objects.for_display())
+def get_shipping_options(request, cart):
     country_form = CountryForm(request.POST or None)
     if country_form.is_valid():
         shipments = country_form.get_shipment_options()
-        return JsonResponse({'options': list(shipments)})
+    else:
+        shipments = None
+    ctx = {
+        'default_country_options': shipments,
+        'country_form': country_form}
+    cart_data = get_cart_data(
+        cart, shipments, request.currency, request.discounts)
+    ctx.update(cart_data)
+    return TemplateResponse(
+        request, 'cart/_subtotal_table.html', ctx)
 
 
 @get_or_empty_db_cart()
diff --git a/saleor/shipping/utils.py b/saleor/shipping/utils.py
--- a/saleor/shipping/utils.py
+++ b/saleor/shipping/utils.py
@@ -1,10 +1,15 @@
+from prices import PriceRange
+
 from .models import ShippingMethodCountry
 
 
 def get_shipment_options(country_code):
     shipping_methods_qs = ShippingMethodCountry.objects.select_related(
-        'shipping_method').values('price', 'shipping_method__name')
+        'shipping_method')
     shipping_methods = shipping_methods_qs.filter(country_code=country_code)
     if not shipping_methods.exists():
         shipping_methods = shipping_methods_qs.filter(country_code='')
-    return shipping_methods
+    if shipping_methods:
+        shipping_methods = shipping_methods.values_list('price', flat=True)
+        return PriceRange(
+            min_price=min(shipping_methods), max_price=max(shipping_methods))


Test patch:
diff --git a/tests/test_cart.py b/tests/test_cart.py
--- a/tests/test_cart.py
+++ b/tests/test_cart.py
@@ -20,6 +20,7 @@
 from saleor.cart.views import update
 from saleor.discount.models import Sale
 from saleor.product.models import Category
+from saleor.shipping.utils import get_shipment_options
 
 
 @pytest.fixture()
@@ -718,3 +719,21 @@ def view(request, cart, *args, **kwargs):
     request.user = AnonymousUser()
     decorated_view(request)
     assert Cart.objects.filter(user__isnull=True).count() == 1
+
+
+def test_get_cart_data(request_cart_with_item, shipping_method):
+    shippment_option = get_shipment_options('PL')
+    cart_data = utils.get_cart_data(
+        request_cart_with_item, shippment_option, 'USD', None)
+    assert cart_data['cart_total'] == Price(net=10, currency='USD')
+    assert cart_data['total_with_shipping'].min_price == Price(
+        net=20, currency='USD')
+
+
+def test_get_cart_data_no_shipping(request_cart_with_item):
+    shippment_option = get_shipment_options('PL')
+    cart_data = utils.get_cart_data(
+        request_cart_with_item, shippment_option, 'USD', None)
+    cart_total = cart_data['cart_total']
+    assert cart_total == Price(net=10, currency='USD')
+    assert cart_data['total_with_shipping'].min_price == cart_total