Browse Source

Add OSCAR_MAX_BASKET_QUANTITY_THRESHOLD

master
Patryk Zawadzki 14 years ago
parent
commit
8a38a5ac13
4 changed files with 106 additions and 64 deletions
  1. 33
    15
      oscar/apps/basket/forms.py
  2. 39
    18
      oscar/apps/basket/tests.py
  3. 32
    31
      oscar/apps/basket/views.py
  4. 2
    0
      oscar/defaults.py

+ 33
- 15
oscar/apps/basket/forms.py View File

1
 from django import forms
1
 from django import forms
2
+from django.conf import settings
2
 from django.db.models import get_model
3
 from django.db.models import get_model
3
 from django.utils.translation import gettext_lazy as _
4
 from django.utils.translation import gettext_lazy as _
4
 
5
 
9
 
10
 
10
 class BasketLineForm(forms.ModelForm):
11
 class BasketLineForm(forms.ModelForm):
11
     save_for_later = forms.BooleanField(initial=False, required=False)
12
     save_for_later = forms.BooleanField(initial=False, required=False)
12
-    
13
+
13
     class Meta:
14
     class Meta:
14
         model = basketline_model
15
         model = basketline_model
15
         exclude = ('basket', 'product', 'line_reference', )
16
         exclude = ('basket', 'product', 'line_reference', )
17
 
18
 
18
 class SavedLineForm(forms.ModelForm):
19
 class SavedLineForm(forms.ModelForm):
19
     move_to_basket = forms.BooleanField(initial=False, required=False)
20
     move_to_basket = forms.BooleanField(initial=False, required=False)
20
-    
21
+
21
     class Meta:
22
     class Meta:
22
         model = basketline_model
23
         model = basketline_model
23
         exclude = ('basket', 'product', 'line_reference', 'quantity', )
24
         exclude = ('basket', 'product', 'line_reference', 'quantity', )
25
 
26
 
26
 class BasketVoucherForm(forms.Form):
27
 class BasketVoucherForm(forms.Form):
27
     code = forms.CharField(max_length=128)
28
     code = forms.CharField(max_length=128)
28
-    
29
+
29
     def __init__(self, *args, **kwargs):
30
     def __init__(self, *args, **kwargs):
30
         return super(BasketVoucherForm, self).__init__(*args,**kwargs)
31
         return super(BasketVoucherForm, self).__init__(*args,**kwargs)
31
-        
32
+
32
 
33
 
33
 class ProductSelectionForm(forms.Form):
34
 class ProductSelectionForm(forms.Form):
34
     product_id = forms.IntegerField(min_value=1)
35
     product_id = forms.IntegerField(min_value=1)
35
-    
36
+
36
     def clean_product_id(self):
37
     def clean_product_id(self):
37
         id = self.cleaned_data['product_id']
38
         id = self.cleaned_data['product_id']
38
 
39
 
45
 class AddToBasketForm(forms.Form):
46
 class AddToBasketForm(forms.Form):
46
     product_id = forms.IntegerField(widget=forms.HiddenInput(), min_value=1)
47
     product_id = forms.IntegerField(widget=forms.HiddenInput(), min_value=1)
47
     quantity = forms.IntegerField(initial=1, min_value=1)
48
     quantity = forms.IntegerField(initial=1, min_value=1)
48
-    
49
-    def __init__(self, instance, *args, **kwargs):
49
+
50
+    def __init__(self, basket, instance, *args, **kwargs):
50
         super(AddToBasketForm, self).__init__(*args, **kwargs)
51
         super(AddToBasketForm, self).__init__(*args, **kwargs)
52
+        self.basket = basket
51
         self.instance = instance
53
         self.instance = instance
52
         if instance:
54
         if instance:
53
             if instance.is_group:
55
             if instance.is_group:
54
                 self._create_group_product_fields(instance)
56
                 self._create_group_product_fields(instance)
55
             else:
57
             else:
56
                 self._create_product_fields(instance)
58
                 self._create_product_fields(instance)
57
-    
59
+
58
     def clean_product_id(self):
60
     def clean_product_id(self):
59
         id = self.cleaned_data['product_id']
61
         id = self.cleaned_data['product_id']
60
         product = Product.objects.get(id=id)
62
         product = Product.objects.get(id=id)
62
             raise forms.ValidationError(_("This product is not available for purchase"))
64
             raise forms.ValidationError(_("This product is not available for purchase"))
63
         return id
65
         return id
64
 
66
 
67
+    def clean_quantity(self):
68
+        qty = self.cleaned_data['quantity']
69
+        basket_threshold = settings.OSCAR_MAX_BASKET_QUANTITY_THRESHOLD
70
+        if basket_threshold:
71
+            total_basket_quantity = self.basket.num_items
72
+            max_allowed = basket_threshold - total_basket_quantity
73
+            if qty > max_allowed:
74
+                raise forms.ValidationError(
75
+                    _("Due to technical limitations we are not able to ship"
76
+                      " more than %(threshold)d items in one order. Your basket"
77
+                      " currently has %(basket)d items.") % {
78
+                            'threshold': basket_threshold,
79
+                            'basket': total_basket_quantity,
80
+                    })
81
+        return qty
82
+
65
     def _create_group_product_fields(self, item):
83
     def _create_group_product_fields(self, item):
66
         """
84
         """
67
         Adds the fields for a "group"-type product (eg, a parent product with a
85
         Adds the fields for a "group"-type product (eg, a parent product with a
70
         choices = []
88
         choices = []
71
         for variant in item.variants.all():
89
         for variant in item.variants.all():
72
             if variant.has_stockrecord:
90
             if variant.has_stockrecord:
73
-                summary = u"%s (%s) - %.2f" % (variant.get_title(), variant.attribute_summary(), 
91
+                summary = u"%s (%s) - %.2f" % (variant.get_title(), variant.attribute_summary(),
74
                                                variant.stockrecord.price_incl_tax)
92
                                                variant.stockrecord.price_incl_tax)
75
                 choices.append((variant.id, summary))
93
                 choices.append((variant.id, summary))
76
         self.fields['product_id'] = forms.ChoiceField(choices=tuple(choices))
94
         self.fields['product_id'] = forms.ChoiceField(choices=tuple(choices))
77
-    
95
+
78
     def _create_product_fields(self, item):
96
     def _create_product_fields(self, item):
79
         u"""Add the product option fields."""
97
         u"""Add the product option fields."""
80
         for option in item.options:
98
         for option in item.options:
81
             self._add_option_field(item, option)
99
             self._add_option_field(item, option)
82
-    
100
+
83
     def _add_option_field(self, item, option):
101
     def _add_option_field(self, item, option):
84
         u"""
102
         u"""
85
         Creates the appropriate form field for the product option.
103
         Creates the appropriate form field for the product option.
86
-        
87
-        This is designed to be overridden so that specific widgets can be used for 
104
+
105
+        This is designed to be overridden so that specific widgets can be used for
88
         certain types of options.
106
         certain types of options.
89
         """
107
         """
90
-        self.fields[option.code] = forms.CharField()    
91
-    
108
+        self.fields[option.code] = forms.CharField()
109
+
92
 
110
 

+ 39
- 18
oscar/apps/basket/tests.py View File

1
 from decimal import Decimal as D
1
 from decimal import Decimal as D
2
 
2
 
3
-from django.utils import unittest
4
-from django.test.client import Client
3
+from django.conf import settings
5
 from django.core.urlresolvers import reverse
4
 from django.core.urlresolvers import reverse
5
+from django.test import TestCase
6
 
6
 
7
-from oscar.apps.basket.models import Basket, Line 
7
+from oscar.apps.basket.models import Basket, Line
8
 from oscar.test.helpers import create_product, TwillTestCase
8
 from oscar.test.helpers import create_product, TwillTestCase
9
 
9
 
10
 
10
 
17
         self.assertPageTitleMatches('Oscar')
17
         self.assertPageTitleMatches('Oscar')
18
 
18
 
19
 
19
 
20
-class BasketModelTest(unittest.TestCase):
21
-    
20
+class BasketModelTest(TestCase):
21
+
22
     def setUp(self):
22
     def setUp(self):
23
         self.basket = Basket.objects.create()
23
         self.basket = Basket.objects.create()
24
         self.dummy_product = create_product()
24
         self.dummy_product = create_product()
25
-    
25
+
26
     def test_empty_baskets_have_zero_lines(self):
26
     def test_empty_baskets_have_zero_lines(self):
27
         self.assertTrue(Basket().num_lines == 0)
27
         self.assertTrue(Basket().num_lines == 0)
28
-        
28
+
29
     def test_new_baskets_are_empty(self):
29
     def test_new_baskets_are_empty(self):
30
         self.assertTrue(Basket().is_empty)
30
         self.assertTrue(Basket().is_empty)
31
-        
31
+
32
     def test_basket_have_with_one_line(self):
32
     def test_basket_have_with_one_line(self):
33
         Line.objects.create(basket=self.basket, product=self.dummy_product)
33
         Line.objects.create(basket=self.basket, product=self.dummy_product)
34
         self.assertTrue(self.basket.num_lines == 1)
34
         self.assertTrue(self.basket.num_lines == 1)
35
-        
35
+
36
     def test_add_product_creates_line(self):
36
     def test_add_product_creates_line(self):
37
         self.basket.add_product(self.dummy_product)
37
         self.basket.add_product(self.dummy_product)
38
         self.assertTrue(self.basket.num_lines == 1)
38
         self.assertTrue(self.basket.num_lines == 1)
39
-        
39
+
40
     def test_adding_multiproduct_line_returns_correct_number_of_items(self):
40
     def test_adding_multiproduct_line_returns_correct_number_of_items(self):
41
         self.basket.add_product(self.dummy_product, 10)
41
         self.basket.add_product(self.dummy_product, 10)
42
         self.assertEqual(self.basket.num_items, 10)
42
         self.assertEqual(self.basket.num_items, 10)
43
-       
44
-        
45
-class BasketViewsTest(unittest.TestCase):
46
-    
47
-    def setUp(self):
48
-        self.client = Client()
49
-    
43
+
44
+
45
+class BasketViewsTest(TestCase):
46
+
50
     def test_empty_basket_view(self):
47
     def test_empty_basket_view(self):
51
         url = reverse('basket:summary')
48
         url = reverse('basket:summary')
52
         response = self.client.get(url)
49
         response = self.client.get(url)
53
         self.assertEquals(200, response.status_code)
50
         self.assertEquals(200, response.status_code)
54
         self.assertEquals(0, response.context['basket'].num_lines)
51
         self.assertEquals(0, response.context['basket'].num_lines)
55
-        
52
+
56
     def test_anonymous_add_to_basket_creates_cookie(self):
53
     def test_anonymous_add_to_basket_creates_cookie(self):
57
         dummy_product = create_product(price=D('10.00'))
54
         dummy_product = create_product(price=D('10.00'))
58
         url = reverse('basket:add')
55
         url = reverse('basket:add')
61
                        'quantity': 1}
58
                        'quantity': 1}
62
         response = self.client.post(url, post_params)
59
         response = self.client.post(url, post_params)
63
         self.assertTrue('oscar_open_basket' in response.cookies)
60
         self.assertTrue('oscar_open_basket' in response.cookies)
61
+
62
+class BasketThresholdTest(TestCase):
63
+
64
+    def setUp(self):
65
+        self._old_threshold = settings.OSCAR_MAX_BASKET_QUANTITY_THRESHOLD
66
+        settings.OSCAR_MAX_BASKET_QUANTITY_THRESHOLD = 3
67
+
68
+    def tearDown(self):
69
+        settings.OSCAR_MAX_BASKET_QUANTITY_THRESHOLD = self._old_threshold
70
+
71
+    def test_adding_more_than_threshold_raises(self):
72
+        dummy_product = create_product(price=D('10.00'))
73
+        url = reverse('basket:add')
74
+        post_params = {'product_id': dummy_product.id,
75
+                       'action': 'add',
76
+                       'quantity': 2}
77
+        response = self.client.post(url, post_params)
78
+        self.assertTrue('oscar_open_basket' in response.cookies)
79
+        post_params = {'product_id': dummy_product.id,
80
+                       'action': 'add',
81
+                       'quantity': 2}
82
+        response = self.client.post(url, post_params)
83
+        self.assertTrue('Your basket currently has 2 items.' in
84
+                        response.cookies['messages'].value)

+ 32
- 31
oscar/apps/basket/views.py View File

22
     extra = 0
22
     extra = 0
23
     can_delete = True
23
     can_delete = True
24
     template_name='basket/basket.html'
24
     template_name='basket/basket.html'
25
-    
25
+
26
     def get_queryset(self):
26
     def get_queryset(self):
27
         return self.request.basket.lines.all()
27
         return self.request.basket.lines.all()
28
-    
28
+
29
     def get_context_data(self, **kwargs):
29
     def get_context_data(self, **kwargs):
30
         context = super(BasketView, self).get_context_data(**kwargs)
30
         context = super(BasketView, self).get_context_data(**kwargs)
31
         context['voucher_form'] = BasketVoucherForm()
31
         context['voucher_form'] = BasketVoucherForm()
32
-        
32
+
33
         if self.request.user.is_authenticated():
33
         if self.request.user.is_authenticated():
34
             try:
34
             try:
35
                 saved_basket = self.basket_model.saved.get(owner=self.request.user)
35
                 saved_basket = self.basket_model.saved.get(owner=self.request.user)
40
             except self.basket_model.DoesNotExist:
40
             except self.basket_model.DoesNotExist:
41
                 pass
41
                 pass
42
         return context
42
         return context
43
-    
43
+
44
     def get_success_url(self):
44
     def get_success_url(self):
45
         return self.request.META.get('HTTP_REFERER', reverse('basket:summary'))
45
         return self.request.META.get('HTTP_REFERER', reverse('basket:summary'))
46
 
46
 
51
                 line = form.instance
51
                 line = form.instance
52
                 if self.request.user.is_authenticated():
52
                 if self.request.user.is_authenticated():
53
                     self.move_line_to_saved_basket(line)
53
                     self.move_line_to_saved_basket(line)
54
-                    messages.info(self.request, _(u"'%(title)s' has been saved for later" % {'title': line.product}))   
54
+                    messages.info(self.request, _(u"'%(title)s' has been saved for later" % {'title': line.product}))
55
                 else:
55
                 else:
56
                     needs_auth = True
56
                     needs_auth = True
57
         if needs_auth:
57
         if needs_auth:
58
-            messages.error(self.request, "You can't save an item for later if you're not logged in!")     
58
+            messages.error(self.request, "You can't save an item for later if you're not logged in!")
59
         return super(BasketView, self).formset_valid(formset)
59
         return super(BasketView, self).formset_valid(formset)
60
 
60
 
61
     def move_line_to_saved_basket(self, line):
61
     def move_line_to_saved_basket(self, line):
69
 
69
 
70
 class BasketAddView(FormView):
70
 class BasketAddView(FormView):
71
     """
71
     """
72
-    Handles the add-to-basket operation, shouldn't be accessed via 
72
+    Handles the add-to-basket operation, shouldn't be accessed via
73
     GET because there's nothing sensible to render.
73
     GET because there's nothing sensible to render.
74
     """
74
     """
75
     form_class = AddToBasketForm
75
     form_class = AddToBasketForm
76
     product_select_form_class = ProductSelectionForm
76
     product_select_form_class = ProductSelectionForm
77
     product_model = get_model('catalogue', 'product')
77
     product_model = get_model('catalogue', 'product')
78
     add_signal = basket_addition
78
     add_signal = basket_addition
79
-    
79
+
80
     def get(self, request, *args, **kwargs):
80
     def get(self, request, *args, **kwargs):
81
         return HttpResponseRedirect(reverse('basket:summary'))
81
         return HttpResponseRedirect(reverse('basket:summary'))
82
-    
83
-    def get_form_kwargs(self): 
82
+
83
+    def get_form_kwargs(self):
84
         kwargs = super(BasketAddView, self).get_form_kwargs()
84
         kwargs = super(BasketAddView, self).get_form_kwargs()
85
         product_select_form = self.product_select_form_class(self.request.POST)
85
         product_select_form = self.product_select_form_class(self.request.POST)
86
 
86
 
87
         if product_select_form.is_valid():
87
         if product_select_form.is_valid():
88
             kwargs['instance'] = product_select_form.cleaned_data['product_id']
88
             kwargs['instance'] = product_select_form.cleaned_data['product_id']
89
         else:
89
         else:
90
-             raise Http404()
90
+            raise Http404()
91
+        kwargs['basket'] = self.request.basket
91
         return kwargs
92
         return kwargs
92
-    
93
+
93
     def get_success_url(self):
94
     def get_success_url(self):
94
         return self.request.META.get('HTTP_REFERER', reverse('basket:summary'))
95
         return self.request.META.get('HTTP_REFERER', reverse('basket:summary'))
95
 
96
 
100
                 options.append({'option': option, 'value': form.cleaned_data[option.code]})
101
                 options.append({'option': option, 'value': form.cleaned_data[option.code]})
101
         self.request.basket.add_product(form.instance, form.cleaned_data['quantity'], options)
102
         self.request.basket.add_product(form.instance, form.cleaned_data['quantity'], options)
102
         messages.info(self.request, _(u"'%(title)s' (quantity %(quantity)d) has been added to your basket" %
103
         messages.info(self.request, _(u"'%(title)s' (quantity %(quantity)d) has been added to your basket" %
103
-                {'title': form.instance.get_title(), 
104
+                {'title': form.instance.get_title(),
104
                  'quantity': form.cleaned_data['quantity']}))
105
                  'quantity': form.cleaned_data['quantity']}))
105
-        
106
+
106
         # Send signal for basket addition
107
         # Send signal for basket addition
107
         self.add_signal.send(sender=self, product=form.instance, user=self.request.user)
108
         self.add_signal.send(sender=self, product=form.instance, user=self.request.user)
108
-        
109
+
109
         return super(BasketAddView, self).form_valid(form)
110
         return super(BasketAddView, self).form_valid(form)
110
-    
111
+
111
     def form_invalid(self, form):
112
     def form_invalid(self, form):
112
         msgs = []
113
         msgs = []
113
         for error in form.errors.values():
114
         for error in form.errors.values():
114
-            msgs.append(error.as_text()) 
115
+            msgs.append(error.as_text())
115
         messages.error(self.request, ",".join(msgs))
116
         messages.error(self.request, ",".join(msgs))
116
         return HttpResponseRedirect(self.request.META.get('HTTP_REFERER',reverse('basket:summary')))
117
         return HttpResponseRedirect(self.request.META.get('HTTP_REFERER',reverse('basket:summary')))
117
 
118
 
120
     model = get_model('voucher', 'voucher')
121
     model = get_model('voucher', 'voucher')
121
     can_delete = True
122
     can_delete = True
122
     extra = 0
123
     extra = 0
123
-    
124
+
124
     def get_queryset(self):
125
     def get_queryset(self):
125
         self.request.basket.vouchers.all()
126
         self.request.basket.vouchers.all()
126
 
127
 
128
 class VoucherAddView(FormView):
129
 class VoucherAddView(FormView):
129
     form_class = BasketVoucherForm
130
     form_class = BasketVoucherForm
130
     voucher_model = get_model('voucher', 'voucher')
131
     voucher_model = get_model('voucher', 'voucher')
131
-    
132
+
132
     def get(self, request, *args, **kwargs):
133
     def get(self, request, *args, **kwargs):
133
         return HttpResponseRedirect(reverse('basket:summary'))
134
         return HttpResponseRedirect(reverse('basket:summary'))
134
-    
135
+
135
     def apply_voucher_to_basket(self, voucher):
136
     def apply_voucher_to_basket(self, voucher):
136
         if not voucher.is_active():
137
         if not voucher.is_active():
137
             messages.error(self.request, _("The '%(code)s' voucher has expired" % {'code': voucher.code}))
138
             messages.error(self.request, _("The '%(code)s' voucher has expired" % {'code': voucher.code}))
138
             return
139
             return
139
-        
140
+
140
         is_available, message = voucher.is_available_to_user(self.request.user)
141
         is_available, message = voucher.is_available_to_user(self.request.user)
141
         if not is_available:
142
         if not is_available:
142
             messages.error(self.request, message)
143
             messages.error(self.request, message)
143
             return
144
             return
144
-        
145
+
145
         self.request.basket.vouchers.add(voucher)
146
         self.request.basket.vouchers.add(voucher)
146
-        
147
+
147
         # Recalculate discounts to see if the voucher gives any
148
         # Recalculate discounts to see if the voucher gives any
148
         discounts_before = self.request.basket.get_discounts()
149
         discounts_before = self.request.basket.get_discounts()
149
         self.request.basket.remove_discounts()
150
         self.request.basket.remove_discounts()
150
         Applicator().apply(self.request, self.request.basket)
151
         Applicator().apply(self.request, self.request.basket)
151
         discounts_after = self.request.basket.get_discounts()
152
         discounts_after = self.request.basket.get_discounts()
152
-        
153
+
153
         # Look for discounts from this new voucher
154
         # Look for discounts from this new voucher
154
         found_discount = False
155
         found_discount = False
155
         for discount in discounts_after:
156
         for discount in discounts_after:
161
             self.request.basket.vouchers.remove(voucher)
162
             self.request.basket.vouchers.remove(voucher)
162
         else:
163
         else:
163
             messages.info(self.request, _("Voucher '%(code)s' added to basket" % {'code': voucher.code}))
164
             messages.info(self.request, _("Voucher '%(code)s' added to basket" % {'code': voucher.code}))
164
-    
165
+
165
     def form_valid(self, form):
166
     def form_valid(self, form):
166
         code = form.cleaned_data['code']
167
         code = form.cleaned_data['code']
167
         if not self.request.basket.id:
168
         if not self.request.basket.id:
173
                 voucher = self.voucher_model._default_manager.get(code=code)
174
                 voucher = self.voucher_model._default_manager.get(code=code)
174
             except self.voucher_model.DoesNotExist:
175
             except self.voucher_model.DoesNotExist:
175
                 messages.error(self.request, _("No voucher found with code '%(code)s'" % {'code': code}))
176
                 messages.error(self.request, _("No voucher found with code '%(code)s'" % {'code': code}))
176
-            else:        
177
+            else:
177
                 self.apply_voucher_to_basket(voucher)
178
                 self.apply_voucher_to_basket(voucher)
178
         return HttpResponseRedirect(self.request.META.get('HTTP_REFERER', reverse('basket:summary')))
179
         return HttpResponseRedirect(self.request.META.get('HTTP_REFERER', reverse('basket:summary')))
179
 
180
 
180
     def form_invalid(self, form):
181
     def form_invalid(self, form):
181
         return HttpResponseRedirect(reverse('basket:summary'))
182
         return HttpResponseRedirect(reverse('basket:summary'))
182
-    
183
+
183
 
184
 
184
 class VoucherRemoveView(View):
185
 class VoucherRemoveView(View):
185
     voucher_model = get_model('voucher', 'voucher')
186
     voucher_model = get_model('voucher', 'voucher')
186
-    
187
+
187
     def get(self, request, *args, **kwargs):
188
     def get(self, request, *args, **kwargs):
188
         return HttpResponseRedirect(reverse('basket:summary'))
189
         return HttpResponseRedirect(reverse('basket:summary'))
189
-    
190
+
190
     def post(self, request, *args, **kwargs):
191
     def post(self, request, *args, **kwargs):
191
         voucher_id = int(kwargs.pop('pk'))
192
         voucher_id = int(kwargs.pop('pk'))
192
         if not request.basket.id:
193
         if not request.basket.id:
220
             return saved_basket.lines.all().select_related('product', 'product__stockrecord')
221
             return saved_basket.lines.all().select_related('product', 'product__stockrecord')
221
         except self.basket_model.DoesNotExist:
222
         except self.basket_model.DoesNotExist:
222
             return []
223
             return []
223
-        
224
+
224
     def get_success_url(self):
225
     def get_success_url(self):
225
         return self.request.META.get('HTTP_REFERER', reverse('basket:summary'))
226
         return self.request.META.get('HTTP_REFERER', reverse('basket:summary'))
226
 
227
 
228
         for form in formset:
229
         for form in formset:
229
             if form.cleaned_data['move_to_basket']:
230
             if form.cleaned_data['move_to_basket']:
230
                 msg = "'%s' has been moved back to your basket" % form.instance.product
231
                 msg = "'%s' has been moved back to your basket" % form.instance.product
231
-                messages.info(self.request, msg)                
232
+                messages.info(self.request, msg)
232
                 real_basket = self.request.basket
233
                 real_basket = self.request.basket
233
                 real_basket.merge_line(form.instance)
234
                 real_basket.merge_line(form.instance)
234
         return super(SavedView, self).formset_valid(formset)
235
         return super(SavedView, self).formset_valid(formset)

+ 2
- 0
oscar/defaults.py View File

42
 # Offers
42
 # Offers
43
 OSCAR_OFFER_BLACKLIST_PRODUCT = None
43
 OSCAR_OFFER_BLACKLIST_PRODUCT = None
44
 
44
 
45
+# Max total number of items in basket
46
+OSCAR_MAX_BASKET_QUANTITY_THRESHOLD = None

Loading…
Cancel
Save