Repository: saleor/saleor

Issue:
TypeError: n.cart is undefined. JS not working in cart
### What I'm trying to achieve
I'm trying run from Docker development guide https://saleor.readthedocs.io/en/latest/customization/docker.html#docker-dev

### Steps to reproduce the problem
1. git clone https://github.com/mirumee/saleor.git; cd saleor/
2. 
sudo docker-compose build
sudo docker-compose run --rm web python3 manage.py migrate
sudo docker-compose run --rm web python3 manage.py collectstatic
sudo docker-compose run --rm web python3 manage.py populatedb --createsuperuser
sudo docker-compose up
open in browser http://127.0.0.1:8000

### What I expected to happen
I want to remove the item from the cart, but it is not removed. After reloading the page it is not in the cart. So JS not works. I disable ad-block on this site http://127.0.0.1:8000/en/checkout/

### Screenshots
Firefox:
![2019-04-22-093409_1918x232_scrot](https://user-images.githubusercontent.com/359276/56487196-11c52f80-64e3-11e9-82c7-6656f18bb7bb.png)
Chromium:
![2019-04-22-094009_932x257_scrot](https://user-images.githubusercontent.com/359276/56487195-11c52f80-64e3-11e9-937a-9b7423596640.png)

**System information**
Operating system: Artix Linux (archlinux-based)
Browser: firefox 66.0.3, chromium 73.0.3683.103



Discussion:
```
Traceback:

File "/Users/Development/saleor-venv/lib/python3.7/site-packages/django/core/handlers/exception.py" in inner
  34.             response = get_response(request)

File "/Users/Development/saleor-venv/lib/python3.7/site-packages/django/core/handlers/base.py" in _get_response
  115.                 response = self.process_exception_by_middleware(e, request)

File "/Users/Development/saleor-venv/lib/python3.7/site-packages/django/core/handlers/base.py" in _get_response
  113.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/Users/Development/saleor/saleor/checkout/utils.py" in func
  235.             return view(request, checkout, *args, **kwargs)

File "/Users/Development/saleor/saleor/checkout/views/__init__.py" in update_checkout_line
  190.         updated_line = checkout.get_line(form.checkout_line.variant)

Exception Type: AttributeError at /en/checkout/update/195/
Exception Value: 'NoneType' object has no attribute 'variant'
```
I will provide a fix on that tomorrow. Thanks for reporting!

Resolving patch:
diff --git a/saleor/checkout/forms.py b/saleor/checkout/forms.py
--- a/saleor/checkout/forms.py
+++ b/saleor/checkout/forms.py
@@ -113,9 +113,7 @@ class ReplaceCheckoutLineForm(AddToCheckoutForm):
 
     def __init__(self, *args, **kwargs):
         self.variant = kwargs.pop('variant')
-        kwargs['product'] = self.variant.product
-        super().__init__(*args, **kwargs)
-        self.checkout_line = self.checkout.get_line(self.variant)
+        super().__init__(*args, product=self.variant.product, **kwargs)
         self.fields['quantity'].widget.attrs = {
             'min': 1, 'max': settings.MAX_CHECKOUT_LINE_QUANTITY}
 
diff --git a/saleor/checkout/models.py b/saleor/checkout/models.py
--- a/saleor/checkout/models.py
+++ b/saleor/checkout/models.py
@@ -109,7 +109,8 @@ def get_total_weight(self):
 
     def get_line(self, variant):
         """Return a line matching the given variant and data if any."""
-        matching_lines = (line for line in self if line.variant == variant)
+        matching_lines = (
+            line for line in self if line.variant.pk == variant.pk)
         return next(matching_lines, None)
 
     def get_last_active_payment(self):
diff --git a/saleor/checkout/views/__init__.py b/saleor/checkout/views/__init__.py
--- a/saleor/checkout/views/__init__.py
+++ b/saleor/checkout/views/__init__.py
@@ -6,7 +6,6 @@
 from ...account.forms import LoginForm
 from ...core.utils import (
     format_money, get_user_shipping_country, to_local_currency)
-from ...product.models import ProductVariant
 from ...shipping.utils import get_shipping_price_estimate
 from ..forms import (
     CheckoutShippingMethodForm, CountryForm, ReplaceCheckoutLineForm)
@@ -166,37 +165,41 @@ def checkout_shipping_options(request, checkout):
     return TemplateResponse(request, 'checkout/_subtotal_table.html', ctx)
 
 
-@get_or_empty_db_checkout()
+@get_or_empty_db_checkout(
+    Checkout.objects.prefetch_related('lines__variant__product'))
 def update_checkout_line(request, checkout, variant_id):
     """Update the line quantities."""
     if not request.is_ajax():
         return redirect('checkout:index')
-    variant = get_object_or_404(ProductVariant, pk=variant_id)
+
+    checkout_line = get_object_or_404(checkout.lines, variant_id=variant_id)
     discounts = request.discounts
     taxes = request.taxes
     status = None
     form = ReplaceCheckoutLineForm(
-        request.POST, checkout=checkout, variant=variant, discounts=discounts,
+        request.POST,
+        checkout=checkout,
+        variant=checkout_line.variant,
+        discounts=discounts,
         taxes=taxes)
     if form.is_valid():
         form.save()
         response = {
             'variantId': variant_id,
-            'subtotal': 0,
+            'subtotal': format_money(
+                checkout_line.get_total(discounts, taxes).gross),
             'total': 0,
             'checkout': {
                 'numItems': checkout.quantity,
                 'numLines': len(checkout)}}
-        updated_line = checkout.get_line(form.checkout_line.variant)
-        if updated_line:
-            response['subtotal'] = format_money(
-                updated_line.get_total(discounts, taxes).gross)
-        if checkout:
-            checkout_total = checkout.get_subtotal(discounts, taxes)
-            response['total'] = format_money(checkout_total.gross)
-            local_checkout_total = to_local_currency(checkout_total, request.currency)
-            if local_checkout_total is not None:
-                response['localTotal'] = format_money(local_checkout_total.gross)
+
+        checkout_total = checkout.get_subtotal(discounts, taxes)
+        response['total'] = format_money(checkout_total.gross)
+        local_checkout_total = to_local_currency(
+            checkout_total, request.currency)
+        if local_checkout_total is not None:
+            response['localTotal'] = format_money(local_checkout_total.gross)
+
         status = 200
     elif request.POST is not None:
         response = {'error': form.errors}
@@ -241,4 +244,4 @@ def prepare_line_data(line):
             'total': checkout.get_subtotal(discounts, taxes),
             'lines': [prepare_line_data(line) for line in checkout]}
 
-    return render(request, 'cart_dropdown.html', data)
+    return render(request, 'checkout_dropdown.html', data)


Test patch:
diff --git a/tests/test_cart.py b/tests/test_cart.py
--- a/tests/test_cart.py
+++ b/tests/test_cart.py
@@ -487,6 +487,14 @@ def test_view_invalid_update_checkout(client, request_checkout_with_item):
     assert request_checkout_with_item.quantity == 1
 
 
+def test_view_invalid_variant_update_checkout(
+        client, request_checkout_with_item):
+    response = client.post(
+        reverse('checkout:update-line', kwargs={'variant_id': '123'}),
+        data={}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+    assert response.status_code == 404
+
+
 def test_checkout_page_without_openexchagerates(
         client, request_checkout_with_item, settings):
     settings.OPENEXCHANGERATES_API_KEY = None