Browse Source

Fix and deprecate min_child_price_* properties

They are from a time when we had one stockrecord per product, and no
strategy classes. They're used for visual purposes only, and were
forgotten when the codebase was updated. They failed loudly, as reported
in #1592.

We really need to either drop them or add a helper method to the
strategy class, but to ensure backwards-compatibility, I've
re-implemented the naive approach of just sorting by price; blindly
ignoring the many shortcomings.

I also added a deprecation notice.
master
Maik Hoepfel 11 years ago
parent
commit
cd5fd41ba5

+ 8
- 4
docs/source/releases/v1.0.1.rst View File

@@ -7,16 +7,20 @@ This is Oscar 1.0.1, a bug fix release.
7 7
 Bug fixes
8 8
 =========
9 9
 
10
+* `#1553`_: ``from oscar.apps.partner.models import *`` could lead to the
11
+  wrong models being imported.
12
+
10 13
 * `#1556`_: Dashboard order table headers shifted
11 14
 
12 15
 * `#1577`_: The billing address was not being correctly passed through to the
13 16
   `place_order` method.
14 17
 
15
-* `#1553`_: ``from oscar.apps.partner.models import *`` could lead to the
16
-  wrong models being imported.
17
-
18
+* `#1592`_: ``Product.min_child_price_[excl|incl]_tax`` were broken and
19
+  failing loudly. They are not recommended any more, but to ensure
20
+  backwards-compatibility, they have been fixed.
18 21
 
19
-  .. _#1556: https://github.com/django-oscar/django-oscar/issues/1556
20 22
   .. _#1553: https://github.com/django-oscar/django-oscar/issues/1553
23
+  .. _#1556: https://github.com/django-oscar/django-oscar/issues/1556
21 24
   .. _#1577: https://github.com/django-oscar/django-oscar/issues/1577
25
+  .. _#1592: https://github.com/django-oscar/django-oscar/issues/1592
22 26
 

+ 38
- 15
oscar/apps/catalogue/abstract_models.py View File

@@ -23,12 +23,14 @@ from treebeard.mp_tree import MP_Node
23 23
 
24 24
 from oscar.core.decorators import deprecated
25 25
 from oscar.core.utils import slugify
26
-from oscar.core.loading import get_classes, get_model
26
+from oscar.core.loading import get_classes, get_model, get_class
27 27
 from oscar.models.fields import NullCharField, AutoSlugField
28 28
 
29 29
 ProductManager, BrowsableProductManager = get_classes(
30 30
     'catalogue.managers', ['ProductManager', 'BrowsableProductManager'])
31 31
 
32
+Selector = get_class('partner.strategy', 'Selector')
33
+
32 34
 
33 35
 @python_2_unicode_compatible
34 36
 class AbstractProductClass(models.Model):
@@ -480,32 +482,53 @@ class AbstractProduct(models.Model):
480 482
         pairs = [attribute.summary() for attribute in attributes]
481 483
         return ", ".join(pairs)
482 484
 
485
+    # The two properties below are deprecated because determining minimum
486
+    # price is not as trivial as it sounds considering multiple stockrecords,
487
+    # currencies, tax, etc.
488
+    # The current implementation is very naive and only works for a limited
489
+    # set of use cases.
490
+    # At the very least, we should pass in the request and
491
+    # user. Hence, it's best done as an extension to a Strategy class.
492
+    # Once that is accomplished, these properties should be removed.
493
+
483 494
     @property
495
+    @deprecated
484 496
     def min_child_price_incl_tax(self):
485 497
         """
486
-        Return minimum child product price including tax
498
+        Return minimum child product price including tax.
487 499
         """
488
-        return self._min_child_price('price_incl_tax')
500
+        return self._min_child_price('incl_tax')
489 501
 
490 502
     @property
503
+    @deprecated
491 504
     def min_child_price_excl_tax(self):
492 505
         """
493
-        Return minimum child product price excluding tax
506
+        Return minimum child product price excluding tax.
507
+
508
+        This is a very naive approach; see the deprecation notice above. And
509
+        only use it for display purposes (e.g. "new Oscar shirt, prices
510
+        starting from $9.50").
494 511
         """
495
-        return self._min_child_price('price_excl_tax')
512
+        return self._min_child_price('excl_tax')
496 513
 
497
-    def _min_child_price(self, property):
514
+    def _min_child_price(self, prop):
498 515
         """
499
-        Return minimum child product price
516
+        Return minimum child product price.
517
+
518
+        This is for visual purposes only. It ignores currencies, most of the
519
+        Strategy logic for selecting stockrecords, knows nothing about the
520
+        current user or request, etc. It's only here to ensure
521
+        backwards-compatibility; the previous implementation wasn't any
522
+        better.
500 523
         """
501
-        prices = []
502
-        for child in self.children.all():
503
-            if child.has_stockrecords:
504
-                prices.append(getattr(child.stockrecord, property))
505
-        if not prices:
506
-            return None
507
-        prices.sort()
508
-        return prices[0]
524
+        strategy = Selector().strategy()
525
+
526
+        children_stock = strategy.select_children_stockrecords(self)
527
+        prices = [
528
+            strategy.pricing_policy(child, stockrecord)
529
+            for child, stockrecord in children_stock]
530
+        raw_prices = sorted([getattr(price, prop) for price in prices])
531
+        return raw_prices[0] if raw_prices else None
509 532
 
510 533
     # The properties below are based on deprecated naming conventions
511 534
 

+ 14
- 2
tests/integration/catalogue/product_tests.py View File

@@ -7,6 +7,7 @@ from oscar.apps.catalogue.models import (Product, ProductClass,
7 7
                                          ProductAttribute,
8 8
                                          AttributeOption)
9 9
 from oscar.test import factories
10
+from oscar.test.decorators import ignore_deprecation_warnings
10 11
 
11 12
 
12 13
 class ProductTests(TestCase):
@@ -82,8 +83,7 @@ class ChildProductTests(ProductTests):
82 83
 
83 84
     def test_child_products_dont_need_titles(self):
84 85
         Product.objects.create(
85
-            parent=self.parent, product_class=self.product_class,
86
-            structure=Product.CHILD)
86
+            parent=self.parent, structure=Product.CHILD)
87 87
 
88 88
     def test_child_products_dont_need_a_product_class(self):
89 89
         Product.objects.create(parent=self.parent, structure=Product.CHILD)
@@ -103,6 +103,18 @@ class ChildProductTests(ProductTests):
103 103
             structure=Product.CHILD)
104 104
         self.assertEqual(set([self.parent]), set(Product.browsable.all()))
105 105
 
106
+    @ignore_deprecation_warnings
107
+    def test_have_a_minimum_price(self):
108
+        self.assertIsNone(self.parent.min_child_price_excl_tax)
109
+        factories.ProductFactory(
110
+            parent=self.parent, structure=Product.CHILD,
111
+            stockrecords__price_excl_tax=5)
112
+        self.assertEqual(5, self.parent.min_child_price_excl_tax)
113
+        factories.ProductFactory(
114
+            parent=self.parent, structure=Product.CHILD,
115
+            stockrecords__price_excl_tax=3)
116
+        self.assertEqual(3, self.parent.min_child_price_excl_tax)
117
+
106 118
 
107 119
 class TestAChildProduct(TestCase):
108 120
 

Loading…
Cancel
Save