Repository: django-oscar/django-oscar

Issue:
price_excl_tax is not required for when you don't track stock
Why is it that way? I don't get the reason behind why its not required if you are not tracking stock?

Here is the relevant code:

```python
class StockRecordForm(forms.ModelForm):

    def __init__(self, product_class, user, *args, **kwargs):
        # The user kwarg is not used by stock StockRecordForm. We pass it
        # anyway in case one wishes to customise the partner queryset
        self.user = user
        super(StockRecordForm, self).__init__(*args, **kwargs)

        # If not tracking stock, we hide the fields
        if not product_class.track_stock:
            del self.fields['num_in_stock']
            del self.fields['low_stock_threshold']
        else:
            self.fields['price_excl_tax'].required = True
            self.fields['num_in_stock'].required = True

    class Meta:
        model = StockRecord
        exclude = ('product', 'num_allocated')

```



Discussion:
That does seem confusing. I think it's to allow for a hook to compute the price in some other way, but I really don't know.

Yeah that seems to be the case. We just removed the if-del from our fork. I just wanted to raise this concern. We'd love to do a PR but this seems to be a 5 minute fix for you guys so I created a GH issue instead.

+1
Because of this when rebuild search index it raises an error.

`TypeError: unsupported operand type(s) for +: 'NoneType' and 'Decimal'`

I agree this looks like a bug. Marking it as such. I'm afraid I don't have time to look into this now, but it should be easy to override this form via the usual mechanisms.

I'll look into this.

Per comment to StockRecord.price_excl_tax field:

> It is NULLable because some items don't have a fixed price but require a runtime calculation (possible from an external service).

So, I suppose this is the case for not tracking case, but @codeinthehole knows the best, since he implemented this code.

> `TypeError: unsupported operand type(s) for +: 'NoneType' and 'Decimal'`

@balsagoth could you please paste here traceback of the exception? Thanks.

As mentioned above, that could be useful for dynamic price calculation, fetching prices from external service etc. Basket-related issues has been addressed by PR #2283, as for the issue with search index rebuilt - could re-create by myself for tax-inclusive pricing strategy.

Resolving patch:
diff --git a/src/oscar/apps/partner/strategy.py b/src/oscar/apps/partner/strategy.py
--- a/src/oscar/apps/partner/strategy.py
+++ b/src/oscar/apps/partner/strategy.py
@@ -266,7 +266,7 @@ class FixedRateTax(object):
     exponent = D('0.01')  # Default to two decimal places
 
     def pricing_policy(self, product, stockrecord):
-        if not stockrecord:
+        if not stockrecord or stockrecord.price_excl_tax is None:
             return UnavailablePrice()
         rate = self.get_rate(product, stockrecord)
         exponent = self.get_exponent(stockrecord)
@@ -319,7 +319,7 @@ class DeferredTax(object):
     """
 
     def pricing_policy(self, product, stockrecord):
-        if not stockrecord:
+        if not stockrecord or stockrecord.price_excl_tax is None:
             return UnavailablePrice()
         return FixedPrice(
             currency=stockrecord.price_currency,


Test patch:
diff --git a/tests/integration/partner/test_strategy.py b/tests/integration/partner/test_strategy.py
--- a/tests/integration/partner/test_strategy.py
+++ b/tests/integration/partner/test_strategy.py
@@ -7,7 +7,17 @@
 from oscar.apps.basket.models import Line
 
 
-class TestDefaultStrategy(TestCase):
+class ProductWithEmptyPriceTestMixin(object):
+
+    def test_product_with_empty_price(self):
+        product_class = factories.ProductClassFactory(track_stock=False)
+        product = factories.ProductFactory(product_class=product_class, stockrecords=[])
+        factories.StockRecordFactory(price_excl_tax=None, product=product)
+        info = self.strategy.fetch_for_product(product)
+        self.assertFalse(info.availability.is_available_to_buy)
+
+
+class TestDefaultStrategy(ProductWithEmptyPriceTestMixin, TestCase):
 
     def setUp(self):
         self.strategy = strategy.Default()
@@ -114,3 +124,15 @@ def test_specifies_correct_availability_code(self):
 
     def test_specifies_product_has_correct_price(self):
         self.assertEqual(D('10.00'), self.info.price.incl_tax)
+
+
+class TestFixedRateTax(ProductWithEmptyPriceTestMixin, TestCase):
+
+    def setUp(self):
+        self.strategy = strategy.UK()
+
+
+class TestDeferredTax(ProductWithEmptyPriceTestMixin, TestCase):
+
+    def setUp(self):
+        self.strategy = strategy.US()