Sfoglia il codice sorgente

Adjust how shipping methods are handled in the repository class

The old Repository class wasn't thread safe as the method instances were
created at compile time.  Since the instance is configured by setting
the basket for each request, this poses thread safety issues as two
requests could share the same method instance (one of which would have
the wrong basket assigned).

This change fiddles with how the methods are handled, ensuring that they
are instanciated within the instance methods so that thread-safety
issues go away.

Shipping methods need a gentle rewrite for v0.7 to ensure they aren't
vulnerable to such issues.
master
David Winterbottom 12 anni fa
parent
commit
ed28ecad71

+ 32
- 1
docs/source/releases/v0.6.rst Vedi File

@@ -239,7 +239,38 @@ Several classes and functions have had signature changes:
239 239
   :class:`oscar.apps.catalogue.reviews.forms.ProductReviewForm``.
240 240
 
241 241
 
242
-Address model changes
242
+Loading shipping methods
243
+~~~~~~~~~~~~~~~~~~~~~~~~
244
+
245
+The default implementation of the shipping method repository has been adjusted
246
+to avoid thread-safety issues.  If you define your own shipping ``Repository``
247
+class, ensure that your shipping methods are instantiated per-request and not
248
+at compile time.
249
+
250
+For example, avoid this:
251
+
252
+.. code-block:: python
253
+
254
+   from oscar.apps.shipping import repository
255
+
256
+   class Repository(repository.Repository)
257
+       methods = [SomeMethod(), AnotherMethod()]
258
+
259
+Instead, instantiate the methods within ``get_shipping_methods``:
260
+
261
+.. code-block:: python
262
+
263
+   from oscar.apps.shipping import repository
264
+
265
+   class Repository(repository.Repository)
266
+       # Note, methods are not instantiated
267
+       methods = [SomeMethod, AnotherMethod]
268
+
269
+Beware of shipping methods that are configured via constructor arguments, like 
270
+:class:`~oscar.apps.shipping.methods.FixedPrice`.  Shipping methods will be
271
+reworked for Oscar 0.7.
272
+
273
+Address Model changes
243 274
 ~~~~~~~~~~~~~~~~~~~~~
244 275
 
245 276
 * The ``UserAddress.salutation`` and ``UserAddress.name`` methods are now

+ 10
- 4
oscar/apps/shipping/repository.py Vedi File

@@ -11,7 +11,9 @@ class Repository(object):
11 11
     Repository class responsible for returning ShippingMethod
12 12
     objects for a given user, basket etc
13 13
     """
14
-    methods = (methods.Free(),)
14
+    # Note, don't instantiate your shipping methods here (at class-level) as
15
+    # that isn't thread safe.
16
+    methods = (methods.Free,)
15 17
 
16 18
     def get_shipping_methods(self, user, basket, shipping_addr=None,
17 19
                              request=None, **kwargs):
@@ -23,7 +25,10 @@ class Repository(object):
23 25
         but this behaviour can easily be overridden by subclassing this class
24 26
         and overriding this method.
25 27
         """
26
-        return self.prime_methods(basket, self.methods)
28
+        # We need to instantiate each method class to avoid thread-safety
29
+        # issues
30
+        methods = [klass() for klass in self.methods]
31
+        return self.prime_methods(basket, methods)
27 32
 
28 33
     def get_default_shipping_method(self, user, basket, shipping_addr=None,
29 34
                                     request=None, **kwargs):
@@ -69,8 +74,9 @@ class Repository(object):
69 74
         """
70 75
         Return the appropriate Method object for the given code
71 76
         """
72
-        for method in self.methods:
73
-            if method.code == code:
77
+        for method_class in self.methods:
78
+            if method_class.code == code:
79
+                method = method_class()
74 80
                 return self.prime_method(basket, method)
75 81
 
76 82
         # Check for NoShippingRequired as that is a special case

+ 10
- 4
tests/integration/order/creator_tests.py Vedi File

@@ -8,7 +8,7 @@ from oscar.apps.catalogue.models import ProductClass, Product
8 8
 from oscar.apps.offer.utils import Applicator
9 9
 from oscar.apps.order.models import Order
10 10
 from oscar.apps.order.utils import OrderCreator
11
-from oscar.apps.shipping.methods import FixedPrice, Free
11
+from oscar.apps.shipping.methods import FixedPrice, Free, Base
12 12
 from oscar.apps.shipping.repository import Repository
13 13
 from oscar.core.loading import get_class
14 14
 from oscar.test import factories
@@ -138,9 +138,15 @@ class TestPlacingOrderForDigitalGoods(TestCase):
138 138
         self.assertTrue(stockrecord.num_allocated is None)
139 139
 
140 140
 
141
+class Fixed(Base):
142
+    code = 'test'
143
+    charge_incl_tax = charge_excl_tax = D('5.00')
144
+    is_tax_known = True
145
+
146
+
141 147
 class StubRepository(Repository):
142 148
     """ Custom shipping methods """
143
-    methods = (FixedPrice(D('5.00'), D('5.00')), Free())
149
+    methods = (Fixed, Free)
144 150
 
145 151
 
146 152
 class TestShippingOfferForOrder(TestCase):
@@ -152,7 +158,7 @@ class TestShippingOfferForOrder(TestCase):
152 158
     def apply_20percent_shipping_offer(self):
153 159
         """Shipping offer 20% off"""
154 160
         range = Range.objects.create(name="All products range",
155
-                                    includes_all_products=True)
161
+                                     includes_all_products=True)
156 162
         benefit = Benefit.objects.create(
157 163
             range=range, type=Benefit.SHIPPING_PERCENTAGE, value=20)
158 164
         offer = factories.create_offer(range=range, benefit=benefit)
@@ -163,7 +169,7 @@ class TestShippingOfferForOrder(TestCase):
163 169
         self.apply_20percent_shipping_offer()
164 170
 
165 171
         # Normal shipping 5.00
166
-        shipping = StubRepository().find_by_code(FixedPrice.code, self.basket)
172
+        shipping = StubRepository().find_by_code('test', self.basket)
167 173
 
168 174
         place_order(self.creator,
169 175
                     basket=self.basket,

Loading…
Annulla
Salva