Przeglądaj źródła

Set maximum line quantity on basket page.

master
Alexander Gaevsky 8 lat temu
rodzic
commit
e60bb4e99b

+ 4
- 0
docs/source/releases/v1.6.rst Wyświetl plik

94
  - For variant products we now display their own images on product detail page
94
  - For variant products we now display their own images on product detail page
95
    and fallback to parent product images if they don't have any.
95
    and fallback to parent product images if they don't have any.
96
 
96
 
97
+ - Basket line form quantity field now has "max" attribute with the maximum
98
+   allowed quantity in it based on product availability and basket threshold
99
+   (see :issue:`1412`).
100
+
97
 .. _incompatible_in_1.6:
101
 .. _incompatible_in_1.6:
98
 
102
 
99
 Backwards incompatible changes in Oscar 1.6
103
 Backwards incompatible changes in Oscar 1.6

+ 17
- 9
src/oscar/apps/basket/abstract_models.py Wyświetl plik

135
                 .order_by(self._meta.pk.name))
135
                 .order_by(self._meta.pk.name))
136
         return self._lines
136
         return self._lines
137
 
137
 
138
-    def is_quantity_allowed(self, qty):
138
+    def max_allowed_quantity(self):
139
         """
139
         """
140
-        Test whether the passed quantity of items can be added to the basket
140
+        Returns maximum product quantity, that can be added to the basket
141
+        with the respect to basket quantity threshold.
141
         """
142
         """
142
-        # We enforce a max threshold to prevent a DOS attack via the offers
143
-        # system.
144
         basket_threshold = settings.OSCAR_MAX_BASKET_QUANTITY_THRESHOLD
143
         basket_threshold = settings.OSCAR_MAX_BASKET_QUANTITY_THRESHOLD
145
         if basket_threshold:
144
         if basket_threshold:
146
             total_basket_quantity = self.num_items
145
             total_basket_quantity = self.num_items
147
             max_allowed = basket_threshold - total_basket_quantity
146
             max_allowed = basket_threshold - total_basket_quantity
148
-            if qty > max_allowed:
149
-                return False, _(
150
-                    "Due to technical limitations we are not able "
151
-                    "to ship more than %(threshold)d items in one order.") \
152
-                    % {'threshold': basket_threshold}
147
+            return max_allowed, basket_threshold
148
+
149
+    def is_quantity_allowed(self, qty):
150
+        """
151
+        Test whether the passed quantity of items can be added to the basket
152
+        """
153
+        # We enforce a max threshold to prevent a DOS attack via the offers
154
+        # system.
155
+        max_allowed, basket_threshold = self.max_allowed_quantity()
156
+        if max_allowed is not None and qty > max_allowed:
157
+            return False, _(
158
+                "Due to technical limitations we are not able "
159
+                "to ship more than %(threshold)d items in one order.") \
160
+                % {'threshold': basket_threshold}
153
         return True, None
161
         return True, None
154
 
162
 
155
     # ============
163
     # ============

+ 10
- 0
src/oscar/apps/basket/forms.py Wyświetl plik

19
         super(BasketLineForm, self).__init__(*args, **kwargs)
19
         super(BasketLineForm, self).__init__(*args, **kwargs)
20
         self.instance.strategy = strategy
20
         self.instance.strategy = strategy
21
 
21
 
22
+        max_allowed_quantity = None
23
+        num_available = getattr(self.instance.purchase_info.availability, 'num_available', None)
24
+        basket_max_allowed_quantity = self.instance.basket.max_allowed_quantity()[0]
25
+        if all([num_available, basket_max_allowed_quantity]):
26
+            max_allowed_quantity = min(num_available, basket_max_allowed_quantity)
27
+        else:
28
+            max_allowed_quantity = num_available or basket_max_allowed_quantity
29
+        if max_allowed_quantity:
30
+            self.fields['quantity'].widget.attrs['max'] = max_allowed_quantity
31
+
22
     def clean_quantity(self):
32
     def clean_quantity(self):
23
         qty = self.cleaned_data['quantity']
33
         qty = self.cleaned_data['quantity']
24
         if qty > 0:
34
         if qty > 0:

+ 19
- 2
tests/integration/basket/test_forms.py Wyświetl plik

1
 from decimal import Decimal as D
1
 from decimal import Decimal as D
2
 
2
 
3
-from django.test import TestCase
3
+from django.test import TestCase, override_settings
4
 from django.conf import settings
4
 from django.conf import settings
5
 import mock
5
 import mock
6
 
6
 
65
         form = self.build_form(quantity=invalid_qty)
65
         form = self.build_form(quantity=invalid_qty)
66
         self.assertFalse(form.is_valid())
66
         self.assertFalse(form.is_valid())
67
 
67
 
68
+    @override_settings(OSCAR_MAX_BASKET_QUANTITY_THRESHOLD=10)
68
     def test_enforce_max_line_quantity_for_existing_product(self):
69
     def test_enforce_max_line_quantity_for_existing_product(self):
69
-        settings.OSCAR_MAX_BASKET_QUANTITY_THRESHOLD = 10
70
         self.basket.flush()
70
         self.basket.flush()
71
         product = factories.create_product(num_in_stock=20)
71
         product = factories.create_product(num_in_stock=20)
72
         add_product(self.basket, D('100'), 4, product)
72
         add_product(self.basket, D('100'), 4, product)
77
         form = self.build_form(quantity=11)
77
         form = self.build_form(quantity=11)
78
         self.assertFalse(form.is_valid())
78
         self.assertFalse(form.is_valid())
79
 
79
 
80
+    def test_line_quantity_max_attribute_per_num_available(self):
81
+        self.basket.flush()
82
+        product = factories.create_product(num_in_stock=20)
83
+        add_product(self.basket, D('100'), 4, product)
84
+        self.line = self.basket.all_lines()[0]
85
+        form = self.build_form()
86
+        self.assertIn('max="20"', str(form['quantity']))
87
+
88
+    @override_settings(OSCAR_MAX_BASKET_QUANTITY_THRESHOLD=10)
89
+    def test_line_quantity_max_attribute_per_basket_threshold(self):
90
+        self.basket.flush()
91
+        product = factories.create_product(num_in_stock=20)
92
+        add_product(self.basket, D('100'), 4, product)
93
+        self.line = self.basket.all_lines()[0]
94
+        form = self.build_form()
95
+        self.assertIn('max="6"', str(form['quantity']))
96
+
80
     def test_basketline_formset_ordering(self):
97
     def test_basketline_formset_ordering(self):
81
         # when we use a unordered queryset in the Basketlineformset, the
98
         # when we use a unordered queryset in the Basketlineformset, the
82
         # discounts will be lost because django will query the database
99
         # discounts will be lost because django will query the database

+ 11
- 1
tests/integration/basket/test_models.py Wyświetl plik

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 from decimal import Decimal as D
2
 from decimal import Decimal as D
3
 
3
 
4
-from django.test import TestCase
4
+from django.test import TestCase, override_settings
5
 
5
 
6
 from oscar.apps.basket.models import Basket
6
 from oscar.apps.basket.models import Basket
7
 from oscar.apps.catalogue.models import Option
7
 from oscar.apps.catalogue.models import Option
242
         self.assertEqual(self.basket.total_excl_tax_excl_discounts, 100)
242
         self.assertEqual(self.basket.total_excl_tax_excl_discounts, 100)
243
         self.assertEqual(self.basket.total_incl_tax_excl_discounts, 100)
243
         self.assertEqual(self.basket.total_incl_tax_excl_discounts, 100)
244
 
244
 
245
+    @override_settings(OSCAR_MAX_BASKET_QUANTITY_THRESHOLD=20)
246
+    def test_max_allowed_quantity(self):
247
+        self.basket.add_product(self.product, quantity=3)
248
+        self.assertEquals(self.basket.max_allowed_quantity()[0], 7)
249
+
250
+    @override_settings(OSCAR_MAX_BASKET_QUANTITY_THRESHOLD=20)
251
+    def test_is_quantity_allowed(self):
252
+        self.assertTrue(self.basket.is_quantity_allowed(qty=3)[0])
253
+        self.assertFalse(self.basket.is_quantity_allowed(qty=11)[0])
254
+
245
 
255
 
246
 class TestMergingTwoBaskets(TestCase):
256
 class TestMergingTwoBaskets(TestCase):
247
 
257
 

Ładowanie…
Anuluj
Zapisz