123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632 |
- import datetime
- from decimal import Decimal as D
- from http import client as http_client
- from http.cookies import _unquote
-
- import django
- from django.contrib.messages.storage import cookie
- from django.core import signing
- from django.test import TestCase, override_settings
- from django.urls import reverse
- from django.utils.translation import gettext
-
- from oscar.apps.basket import reports
- from oscar.apps.basket.models import Basket
- from oscar.apps.partner import strategy
- from oscar.core.compat import get_user_model
- from oscar.test import factories
- from oscar.test.basket import add_product
- from oscar.test.factories import OptionFactory, create_product
- from oscar.test.testcases import WebTestCase
-
- User = get_user_model()
-
-
- class TestBasketMerging(TestCase):
- def setUp(self):
- self.product = create_product(num_in_stock=10)
- self.user_basket = Basket()
- self.user_basket.strategy = strategy.Default()
- add_product(self.user_basket, product=self.product)
- self.cookie_basket = Basket()
- self.cookie_basket.strategy = strategy.Default()
- add_product(self.cookie_basket, quantity=2, product=self.product)
- self.user_basket.merge(self.cookie_basket, add_quantities=False)
-
- def test_cookie_basket_has_status_set(self):
- self.assertEqual(Basket.MERGED, self.cookie_basket.status)
-
- def test_lines_are_moved_across(self):
- self.assertEqual(1, self.user_basket.lines.all().count())
-
- def test_merge_line_takes_max_quantity(self):
- line = self.user_basket.lines.get(product=self.product)
- self.assertEqual(2, line.quantity)
-
-
- class AnonAddToBasketViewTests(WebTestCase):
- csrf_checks = False
-
- def setUp(self):
- self.product = create_product(price=D("10.00"), num_in_stock=10)
- self.url = reverse("basket:add", kwargs={"pk": self.product.pk})
- self.post_params = {
- "product_id": self.product.id,
- "action": "add",
- "quantity": 1,
- }
- self.response = self.app.post(self.url, params=self.post_params)
-
- def test_cookie_is_created(self):
- self.assertTrue("oscar_open_basket" in self.response.test_app.cookies)
-
- def test_price_is_recorded(self):
- oscar_open_basket_cookie = _unquote(
- self.response.test_app.cookies["oscar_open_basket"]
- )
- basket_id = oscar_open_basket_cookie.split(":")[0]
- basket = Basket.objects.get(id=basket_id)
- line = basket.lines.get(product=self.product)
- stockrecord = self.product.stockrecords.all()[0]
- self.assertEqual(stockrecord.price, line.price_excl_tax)
-
-
- class TestMergedBasketsMessage(TestCase):
- def setUp(self):
- product = create_product(price=D("10.00"), num_in_stock=10)
- self.url = reverse("basket:add", args=(product.pk,))
- self.post_params = {
- "product_id": product.id,
- "action": "add",
- "quantity": 1,
- }
- self.user = User.objects.create(
- username="lucy", email="lucy@example.com", password="password"
- )
-
- def test_merged_baskets_message(self):
- # add product to anonymous user's basket
- response = self.client.post(self.url, self.post_params)
- self.assertEqual(response.status_code, 302)
-
- self.assertIn("oscar_open_basket", response.cookies)
- oscar_open_basket_cookie = response.cookies["oscar_open_basket"]
-
- basket = response.context["basket"]
- self.assertEqual(basket.all_lines().count(), 1)
- self.assertIsNone(basket.owner)
-
- # set registered user
- self.client.force_login(self.user)
- sess = self.client.session
- sess["oscar_open_basket"] = oscar_open_basket_cookie
- sess.save()
- self.assertTrue("oscar_open_basket" in self.client.session)
-
- response = self.client.get("/", follow=True)
- self.assertEqual(response.status_code, 200)
-
- basket = response.context["basket"]
- self.assertEqual(basket.all_lines().count(), 1)
- self.assertEqual(basket.owner, self.user)
- self.assertIn("messages", response.cookies)
-
- expected = "We have merged 1 item from a previous session to your basket."
- messages = list(response.context["messages"])
- self.assertEqual(len(messages), 3)
- self.assertEqual(messages[2].message, expected)
-
-
- class BasketSummaryViewTests(WebTestCase):
- def setUp(self):
- url = reverse("basket:summary")
- self.response = self.app.get(url)
-
- def test_shipping_method_in_context(self):
- self.assertTrue("shipping_method" in self.response.context)
-
- def test_order_total_in_context(self):
- self.assertTrue("order_total" in self.response.context)
-
- def test_view_does_not_error(self):
- self.assertEqual(http_client.OK, self.response.status_code)
-
- def test_basket_in_context(self):
- self.assertTrue("basket" in self.response.context)
-
- def test_basket_is_empty(self):
- basket = self.response.context["basket"]
- self.assertEqual(0, basket.num_lines)
-
-
- class BasketThresholdTest(WebTestCase):
- csrf_checks = False
-
- @override_settings(OSCAR_MAX_BASKET_QUANTITY_THRESHOLD=3)
- def test_adding_more_than_threshold_raises(self):
- dummy_product = create_product(price=D("10.00"), num_in_stock=10)
- url = reverse("basket:add", kwargs={"pk": dummy_product.pk})
- post_params = {"product_id": dummy_product.id, "action": "add", "quantity": 2}
- response = self.app.post(url, params=post_params)
- # pylint: disable=no-member
- self.assertIn("oscar_open_basket", response.test_app.cookies)
- post_params = {"product_id": dummy_product.id, "action": "add", "quantity": 2}
- response = self.app.post(url, params=post_params)
- expected = gettext(
- "Due to technical limitations we are not able to ship more "
- "than %(threshold)d items in one order. Your basket currently "
- "has %(basket)d items."
- ) % ({"threshold": 3, "basket": 2})
- if django.VERSION < (3, 2):
- self.assertIn(expected, response.test_app.cookies["messages"])
- else:
- signer = signing.get_cookie_signer(salt="django.contrib.messages")
- message_strings = [
- m.message
- # pylint: disable=no-member
- for m in signer.unsign_object(
- response.test_app.cookies["messages"],
- serializer=cookie.MessageSerializer,
- )
- ]
- self.assertIn(expected, message_strings)
-
-
- class BasketReportTests(TestCase):
- def test_open_report_doesnt_error(self):
- data = {
- "start_date": datetime.date(2012, 5, 1),
- "end_date": datetime.date(2012, 5, 17),
- "formatter": "CSV",
- }
- generator = reports.OpenBasketReportGenerator(**data)
- generator.generate()
-
- def test_submitted_report_doesnt_error(self):
- data = {
- "start_date": datetime.date(2012, 5, 1),
- "end_date": datetime.date(2012, 5, 17),
- "formatter": "CSV",
- }
- generator = reports.SubmittedBasketReportGenerator(**data)
- generator.generate()
-
-
- class SavedBasketTests(WebTestCase):
- csrf_checks = False
-
- def test_moving_to_saved_basket_creates_new(self):
- self.user = factories.UserFactory()
- product = factories.ProductFactory()
- basket = factories.BasketFactory(owner=self.user)
- basket.add_product(product)
-
- response = self.get(reverse("basket:summary"))
- formset = response.context["formset"]
- form = formset.forms[0]
-
- data = {
- formset.add_prefix("INITIAL_FORMS"): 1,
- formset.add_prefix("TOTAL_FORMS"): 1,
- formset.add_prefix("MIN_FORMS"): 0,
- formset.add_prefix("MAX_NUM_FORMS"): 1,
- form.add_prefix("id"): form.instance.pk,
- form.add_prefix("quantity"): form.initial["quantity"],
- form.add_prefix("save_for_later"): True,
- }
- response = self.post(reverse("basket:summary"), params=data)
-
- self.assertRedirects(response, reverse("basket:summary"))
- self.assertFalse(Basket.open.get(pk=basket.pk).lines.exists())
- self.assertEqual(
- Basket.saved.get(owner=self.user).lines.get(product=product).quantity, 1
- )
-
- def test_moving_to_saved_basket_updates_existing(self):
- self.user = factories.UserFactory()
- product = factories.ProductFactory()
-
- basket = factories.BasketFactory(owner=self.user)
- basket.add_product(product)
-
- saved_basket = factories.BasketFactory(owner=self.user, status=Basket.SAVED)
- saved_basket.add_product(product)
-
- response = self.get(reverse("basket:summary"))
- formset = response.context["formset"]
- form = formset.forms[0]
-
- data = {
- formset.add_prefix("INITIAL_FORMS"): 1,
- formset.add_prefix("TOTAL_FORMS"): 1,
- formset.add_prefix("MIN_FORMS"): 0,
- formset.add_prefix("MAX_NUM_FORMS"): 1,
- form.add_prefix("id"): form.instance.pk,
- form.add_prefix("quantity"): form.initial["quantity"],
- form.add_prefix("save_for_later"): True,
- }
- response = self.post(reverse("basket:summary"), params=data)
-
- self.assertRedirects(response, reverse("basket:summary"))
- self.assertFalse(Basket.open.get(pk=basket.pk).lines.exists())
- self.assertEqual(
- Basket.saved.get(pk=saved_basket.pk).lines.get(product=product).quantity, 2
- )
-
- def test_moving_from_saved_basket(self):
- self.user = User.objects.create_user(
- username="test", password="pass", email="test@example.com"
- )
- product = create_product(price=D("10.00"), num_in_stock=2)
- basket = factories.create_basket(empty=True)
- basket.owner = self.user
- basket.save()
- add_product(basket, product=product)
-
- saved_basket, _ = Basket.saved.get_or_create(owner=self.user)
- saved_basket.strategy = basket.strategy
- add_product(saved_basket, product=product)
-
- response = self.get(reverse("basket:summary"))
- saved_formset = response.context["saved_formset"]
- saved_form = saved_formset.forms[0]
-
- data = {
- saved_formset.add_prefix("INITIAL_FORMS"): 1,
- saved_formset.add_prefix("MAX_NUM_FORMS"): 1,
- saved_formset.add_prefix("TOTAL_FORMS"): 1,
- saved_form.add_prefix("id"): saved_form.initial["id"],
- saved_form.add_prefix("move_to_basket"): True,
- }
- response = self.post(reverse("basket:saved"), params=data)
- self.assertEqual(
- Basket.open.get(id=basket.id).lines.get(product=product).quantity, 2
- )
- self.assertRedirects(response, reverse("basket:summary"))
-
- def test_moving_from_saved_basket_more_than_stocklevel_raises(self):
- self.user = User.objects.create_user(
- username="test", password="pass", email="test@example.com"
- )
- product = create_product(price=D("10.00"), num_in_stock=1)
- basket, _ = Basket.open.get_or_create(owner=self.user)
- add_product(basket, product=product)
-
- saved_basket, _ = Basket.saved.get_or_create(owner=self.user)
- add_product(saved_basket, product=product)
-
- response = self.get(reverse("basket:summary"))
- saved_formset = response.context["saved_formset"]
- saved_form = saved_formset.forms[0]
-
- data = {
- saved_formset.add_prefix("INITIAL_FORMS"): 1,
- saved_formset.add_prefix("MAX_NUM_FORMS"): 1,
- saved_formset.add_prefix("TOTAL_FORMS"): 1,
- saved_form.add_prefix("id"): saved_form.initial["id"],
- saved_form.add_prefix("move_to_basket"): True,
- }
- response = self.post(reverse("basket:saved"), params=data)
- # we can't add more than stock level into basket
- self.assertEqual(
- Basket.open.get(id=basket.id).lines.get(product=product).quantity, 1
- )
- self.assertRedirects(response, reverse("basket:summary"))
-
-
- class BasketFormSetTests(WebTestCase):
- csrf_checks = False
-
- def test_formset_with_removed_line(self):
- products = [create_product() for i in range(3)]
- basket = factories.create_basket(empty=True)
- basket.owner = self.user
- basket.save()
-
- add_product(basket, product=products[0])
- add_product(basket, product=products[1])
- add_product(basket, product=products[2])
- response = self.get(reverse("basket:summary"))
- formset = response.context["formset"]
- self.assertEqual(len(formset.forms), 3)
-
- basket.lines.filter(product=products[0]).delete()
-
- management_form = formset.management_form
- data = {
- formset.add_prefix("INITIAL_FORMS"): management_form.initial[
- "INITIAL_FORMS"
- ],
- formset.add_prefix("MAX_NUM_FORMS"): management_form.initial[
- "MAX_NUM_FORMS"
- ],
- formset.add_prefix("TOTAL_FORMS"): management_form.initial["TOTAL_FORMS"],
- "form-0-quantity": 1,
- "form-0-id": formset.forms[0].instance.id,
- "form-1-quantity": 2,
- "form-1-id": formset.forms[1].instance.id,
- "form-2-quantity": 2,
- "form-2-id": formset.forms[2].instance.id,
- }
- response = self.post(reverse("basket:summary"), params=data)
- self.assertEqual(response.status_code, 302)
- formset = response.follow().context["formset"]
- self.assertEqual(len(formset.forms), 2)
- self.assertEqual(len(formset.forms_with_instances), 2)
- self.assertEqual(basket.lines.all()[0].quantity, 2)
- self.assertEqual(basket.lines.all()[1].quantity, 2)
-
- def test_invalid_formset_with_removed_line(self):
- products = [create_product() for i in range(3)]
- basket = factories.create_basket(empty=True)
- basket.owner = self.user
- basket.save()
-
- add_product(basket, product=products[0])
- add_product(basket, product=products[1])
- add_product(basket, product=products[2])
- response = self.get(reverse("basket:summary"))
- formset = response.context["formset"]
- self.assertEqual(len(formset.forms), 3)
-
- basket.lines.filter(product=products[0]).delete()
-
- stockrecord = products[1].stockrecords.first()
- stockrecord.num_in_stock = 0
- stockrecord.save()
-
- management_form = formset.management_form
- data = {
- formset.add_prefix("INITIAL_FORMS"): management_form.initial[
- "INITIAL_FORMS"
- ],
- formset.add_prefix("MIN_NUM_FORMS"): management_form.initial[
- "MIN_NUM_FORMS"
- ],
- formset.add_prefix("MAX_NUM_FORMS"): management_form.initial[
- "MAX_NUM_FORMS"
- ],
- formset.add_prefix("TOTAL_FORMS"): management_form.initial["TOTAL_FORMS"],
- "form-0-quantity": 1,
- "form-0-id": formset.forms[0].instance.id,
- "form-1-quantity": 2,
- "form-1-id": formset.forms[1].instance.id,
- "form-2-quantity": 2,
- "form-2-id": formset.forms[2].instance.id,
- }
- response = self.post(reverse("basket:summary"), params=data)
- self.assertEqual(response.status_code, 200)
- formset = response.context["formset"]
- self.assertEqual(len(formset.forms), 2)
- self.assertEqual(len(formset.forms_with_instances), 2)
- self.assertEqual(basket.lines.all()[0].quantity, 1)
- self.assertEqual(basket.lines.all()[1].quantity, 1)
-
- def test_deleting_valid_line_with_other_valid_line(self):
- product_1 = create_product()
- product_2 = create_product()
-
- basket = factories.create_basket(empty=True)
- basket.owner = self.user
- basket.save()
- add_product(basket, product=product_1)
- add_product(basket, product=product_2)
-
- response = self.get(reverse("basket:summary"))
- formset = response.context["formset"]
- self.assertEqual(len(formset.forms), 2)
-
- data = {
- formset.add_prefix("TOTAL_FORMS"): formset.management_form.initial[
- "TOTAL_FORMS"
- ],
- formset.add_prefix("INITIAL_FORMS"): formset.management_form.initial[
- "INITIAL_FORMS"
- ],
- formset.add_prefix("MIN_NUM_FORMS"): formset.management_form.initial[
- "MIN_NUM_FORMS"
- ],
- formset.add_prefix("MAX_NUM_FORMS"): formset.management_form.initial[
- "MAX_NUM_FORMS"
- ],
- formset.forms[0].add_prefix("id"): formset.forms[0].instance.pk,
- formset.forms[0].add_prefix("quantity"): formset.forms[0].instance.quantity,
- formset.forms[0].add_prefix("DELETE"): "on",
- formset.forms[1].add_prefix("id"): formset.forms[1].instance.pk,
- formset.forms[1].add_prefix("quantity"): formset.forms[1].instance.quantity,
- }
- response = self.post(reverse("basket:summary"), params=data, xhr=True)
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(len(response.context["formset"].forms), 1)
- self.assertFalse(
- response.context["formset"].is_bound
- ) # new formset is rendered
- self.assertEqual(basket.lines.count(), 1)
- self.assertEqual(basket.lines.all()[0].quantity, 1)
-
- def test_deleting_valid_line_with_other_invalid_line(self):
- product_1 = create_product()
- product_2 = create_product()
-
- basket = factories.create_basket(empty=True)
- basket.owner = self.user
- basket.save()
- add_product(basket, product=product_1)
- add_product(basket, product=product_2)
-
- response = self.get(reverse("basket:summary"))
- formset = response.context["formset"]
- self.assertEqual(len(formset.forms), 2)
-
- # Render product for other line out of stock
- product_2.stockrecords.update(num_in_stock=0)
-
- data = {
- formset.add_prefix("TOTAL_FORMS"): formset.management_form.initial[
- "TOTAL_FORMS"
- ],
- formset.add_prefix("INITIAL_FORMS"): formset.management_form.initial[
- "INITIAL_FORMS"
- ],
- formset.add_prefix("MIN_NUM_FORMS"): formset.management_form.initial[
- "MIN_NUM_FORMS"
- ],
- formset.add_prefix("MAX_NUM_FORMS"): formset.management_form.initial[
- "MAX_NUM_FORMS"
- ],
- formset.forms[0].add_prefix("id"): formset.forms[0].instance.pk,
- formset.forms[0].add_prefix("quantity"): formset.forms[0].instance.quantity,
- formset.forms[0].add_prefix("DELETE"): "on",
- formset.forms[1].add_prefix("id"): formset.forms[1].instance.pk,
- formset.forms[1].add_prefix("quantity"): formset.forms[1].instance.quantity,
- }
- response = self.post(reverse("basket:summary"), params=data, xhr=True)
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(len(response.context["formset"].forms), 1)
- self.assertTrue(
- response.context["formset"].is_bound
- ) # formset with errors is rendered
- self.assertFalse(response.context["formset"].forms[0].is_valid())
- self.assertEqual(basket.lines.count(), 1)
- self.assertEqual(basket.lines.all()[0].quantity, 1)
-
- def test_deleting_invalid_line_with_other_valid_line(self):
- product_1 = create_product()
- product_2 = create_product()
-
- basket = factories.create_basket(empty=True)
- basket.owner = self.user
- basket.save()
- add_product(basket, product=product_1)
- add_product(basket, product=product_2)
-
- response = self.get(reverse("basket:summary"))
- formset = response.context["formset"]
- self.assertEqual(len(formset.forms), 2)
-
- # Render product for to-be-deleted line out of stock
- product_1.stockrecords.update(num_in_stock=0)
-
- data = {
- formset.add_prefix("TOTAL_FORMS"): formset.management_form.initial[
- "TOTAL_FORMS"
- ],
- formset.add_prefix("INITIAL_FORMS"): formset.management_form.initial[
- "INITIAL_FORMS"
- ],
- formset.add_prefix("MIN_NUM_FORMS"): formset.management_form.initial[
- "MIN_NUM_FORMS"
- ],
- formset.add_prefix("MAX_NUM_FORMS"): formset.management_form.initial[
- "MAX_NUM_FORMS"
- ],
- formset.forms[0].add_prefix("id"): formset.forms[0].instance.pk,
- formset.forms[0].add_prefix("quantity"): formset.forms[0].instance.quantity,
- formset.forms[0].add_prefix("DELETE"): "on",
- formset.forms[1].add_prefix("id"): formset.forms[1].instance.pk,
- formset.forms[1].add_prefix("quantity"): formset.forms[1].instance.quantity,
- }
- response = self.post(reverse("basket:summary"), params=data, xhr=True)
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(len(response.context["formset"].forms), 1)
- self.assertFalse(
- response.context["formset"].is_bound
- ) # new formset is rendered
- self.assertEqual(basket.lines.count(), 1)
- self.assertEqual(basket.lines.all()[0].quantity, 1)
-
- def test_deleting_invalid_line_with_other_invalid_line(self):
- product_1 = create_product()
- product_2 = create_product()
-
- basket = factories.create_basket(empty=True)
- basket.owner = self.user
- basket.save()
- add_product(basket, product=product_1)
- add_product(basket, product=product_2)
-
- response = self.get(reverse("basket:summary"))
- formset = response.context["formset"]
- self.assertEqual(len(formset.forms), 2)
-
- # Render products for both lines out of stock
- product_1.stockrecords.update(num_in_stock=0)
- product_2.stockrecords.update(num_in_stock=0)
-
- data = {
- formset.add_prefix("TOTAL_FORMS"): formset.management_form.initial[
- "TOTAL_FORMS"
- ],
- formset.add_prefix("INITIAL_FORMS"): formset.management_form.initial[
- "INITIAL_FORMS"
- ],
- formset.add_prefix("MIN_NUM_FORMS"): formset.management_form.initial[
- "MIN_NUM_FORMS"
- ],
- formset.add_prefix("MAX_NUM_FORMS"): formset.management_form.initial[
- "MAX_NUM_FORMS"
- ],
- formset.forms[0].add_prefix("id"): formset.forms[0].instance.pk,
- formset.forms[0].add_prefix("quantity"): formset.forms[0].instance.quantity,
- formset.forms[0].add_prefix("DELETE"): "on",
- formset.forms[1].add_prefix("id"): formset.forms[1].instance.pk,
- formset.forms[1].add_prefix("quantity"): formset.forms[1].instance.quantity,
- }
- response = self.post(reverse("basket:summary"), params=data, xhr=True)
-
- self.assertEqual(response.status_code, 200)
- self.assertEqual(len(response.context["formset"].forms), 1)
- self.assertTrue(
- response.context["formset"].is_bound
- ) # formset with errors is rendered
- self.assertFalse(response.context["formset"].forms[0].is_valid())
- self.assertEqual(basket.lines.count(), 1)
- self.assertEqual(basket.lines.all()[0].quantity, 1)
-
- def test_formset_quantity_update_with_options(self):
- product = create_product(num_in_stock=2)
- option = OptionFactory()
- # Add the option to the product class
- product.get_product_class().options.add(option)
-
- basket = factories.create_basket(empty=True)
- basket.owner = self.user
- basket.save()
- basket.add_product(product, options=[{"option": option, "value": "Test 1"}])
- basket.add_product(product, options=[{"option": option, "value": "Test 2"}])
-
- response = self.get(reverse("basket:summary"))
- formset = response.context["formset"]
- self.assertEqual(len(formset.forms), 2)
-
- # Now update one of the quantities to 2
- data = {
- formset.add_prefix("TOTAL_FORMS"): formset.management_form.initial[
- "TOTAL_FORMS"
- ],
- formset.add_prefix("INITIAL_FORMS"): formset.management_form.initial[
- "INITIAL_FORMS"
- ],
- formset.add_prefix("MIN_NUM_FORMS"): formset.management_form.initial[
- "MIN_NUM_FORMS"
- ],
- formset.add_prefix("MAX_NUM_FORMS"): formset.management_form.initial[
- "MAX_NUM_FORMS"
- ],
- formset.forms[0].add_prefix("id"): formset.forms[0].instance.pk,
- formset.forms[0].add_prefix("quantity"): 2,
- formset.forms[1].add_prefix("id"): formset.forms[1].instance.pk,
- formset.forms[1].add_prefix("quantity"): formset.forms[1].instance.quantity,
- }
- response = self.post(reverse("basket:summary"), params=data, xhr=True)
-
- self.assertEqual(response.status_code, 200)
- self.assertFalse(response.context["formset"].forms[0].is_valid())
- self.assertIn(
- "Available stock is only %s, which has been exceeded because multiple lines contain the same product."
- % 2,
- str(response.context["formset"].forms[0].errors),
- )
|