Pārlūkot izejas kodu

Major tidy up of checkout view classes.

Pulled template variables into a class context variable
Updated shipping method loading
master
David Winterbottom 14 gadus atpakaļ
vecāks
revīzija
aeccbd0499

+ 10
- 1
docs/source/contributing.rst Parādīt failu

@@ -1,12 +1,21 @@
1 1
 Contributing
2 2
 ============
3 3
 
4
-Guidelines:
4
+Make sure you're read http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html
5
+
6
+Guidelines
7
+----------
5 8
 
6 9
 * New features should be discussed on the mailing list (or in the meetings) before serious work starts
7 10
 * Pull requests will be rejected if sufficient tests aren't provided
8 11
 * Please updated the documentation when altering behaviour or introducing new features 
9 12
 
13
+Coding conventions
14
+------------------
15
+
16
+* PEP8 (http://www.python.org/dev/peps/pep-0008/)
17
+* PEP257 (http://www.python.org/dev/peps/pep-0257/)
18
+
10 19
 Contents:
11 20
 
12 21
 .. toctree::

+ 1
- 0
docs/source/index.rst Parādīt failu

@@ -13,6 +13,7 @@ Contents:
13 13
 
14 14
    introduction
15 15
    getting_started
16
+   web_services
16 17
    recipes
17 18
    contributing
18 19
 

+ 47
- 0
docs/source/recipes/custom_shipping_logic.rst Parādīt failu

@@ -0,0 +1,47 @@
1
+Shipping
2
+========
3
+
4
+By default, you can configure shipping by using the built-in ShippingMethod models.  These
5
+support shipping charges that are calculated using an order- and item-level charge.
6
+
7
+Custom shipping calculators
8
+---------------------------
9
+
10
+To use a custom shipping calculator, you need to subclass the core shipping Repository class and
11
+override two methods in provide the calculator of your domain.
12
+
13
+First create a ``myshop.shipping`` app and include it in your ``settings.py`` file (removing the ``oscar.shipping``
14
+app in the process.
15
+
16
+Next, create ``methods.py`` and create a new ``Repository`` class that subclasses the core ``Repository`` class but
17
+provides the custom behaviour that you need.
18
+
19
+Here is an example ``methods.py``::
20
+
21
+    from decimal import Decimal
22
+
23
+    from oscar.shipping.methods import Repository as CoreRepository
24
+    from oscar.shipping.abstract_models import ShippingMethod
25
+    
26
+    class FixedChargeMethod(ShippingMethod):
27
+        
28
+        name = 'Fixed charge'
29
+        
30
+        def basket_charge_incl_tax(self):
31
+            return Decimal('12.50')
32
+        
33
+        def basket_charge_excl_tax(self):
34
+            return Decimal('12.50')
35
+    
36
+    class Repository(CoreRepository):
37
+        
38
+        def __init__(self):
39
+            self.method = FixedChargeMethod()
40
+        
41
+        def get_shipping_methods(self, user, basket):
42
+            return [self.method] 
43
+    
44
+        def find_by_code(self, code):
45
+            return self.method
46
+
47
+Here we are using a plain Python object (not a Django model) as the shipping calculator.

+ 12
- 0
docs/source/web_services.rst Parādīt failu

@@ -0,0 +1,12 @@
1
+Web services
2
+============
3
+
4
+Django-oscar exposes three different RESTful webservices for administration:
5
+
6
+.. toctree::
7
+   :maxdepth: 2
8
+
9
+   web_services/order_management
10
+   web_services/stock_management
11
+   web_services/inventory_management
12
+

+ 3
- 0
docs/source/web_services/inventory_management.rst Parādīt failu

@@ -0,0 +1,3 @@
1
+Inventory management REST services
2
+==============================
3
+

+ 62
- 0
docs/source/web_services/order_management.rst Parādīt failu

@@ -0,0 +1,62 @@
1
+Order management REST services
2
+==============================
3
+
4
+Supported methods and resources:
5
+
6
+**Retrieve list of orders:**::
7
+
8
+    GET /orders/
9
+
10
+Filters:
11
+
12
+* ``after=2010-10-01`` - Return all orders placed after 2010-10-01
13
+* ``before=2010-10-31`` - Return all orders placed before 2010-10-31
14
+
15
+**Retrieve a summary of an order with number 123 (not id)**::
16
+
17
+    GET /order/123/
18
+
19
+**Retrieve a list of batches**::
20
+
21
+    GET /order/123/batches/
22
+
23
+**Retrieve a summary of batch**::
24
+
25
+    GET /order/123/batch/34/
26
+
27
+**Retrieve a list of lines**::
28
+
29
+    GET /order/123/batch/34/lines/ [just lines within batch 34, part of order 123]
30
+    
31
+    GET /order/123/lines/ [all lines within order 123]
32
+    
33
+    GET /lines/ [all lines]
34
+
35
+Filters:
36
+
37
+* ``at_shipping_status`` - Returns lines at the specified shipping status (use the code)
38
+
39
+* ``at_payment_status`` - Returns lines at the specified payment status (use the code)
40
+
41
+* ``partner`` - Returns lines fulfilled by a particular partner
42
+
43
+**Retrieve a summary of a lines with ids 100,101,102**::
44
+
45
+    GET /order/123/batch/34/line/100;101;102`` 
46
+
47
+    GET /order/123/line/100;101;102`` 
48
+
49
+**Update shipping status of an order line**::
50
+
51
+    POST /order/123/batch/34/line/100/ 
52
+
53
+    POST /order/123/lines/ 
54
+
55
+Request:
56
+
57
+``{'shipping_status': 'acknowledged'}`` - Update every item in line
58
+
59
+``{'shipping_status': {'acknowledged': 10, 'cancelled': 1}}`` - Fine-grained control
60
+
61
+
62
+

+ 3
- 0
docs/source/web_services/stock_management.rst Parādīt failu

@@ -0,0 +1,3 @@
1
+Stock management REST services
2
+==============================
3
+

+ 6
- 7
examples/defaultshop/selenium/save-delivery-address Parādīt failu

@@ -13,7 +13,7 @@
13 13
 </thead><tbody>
14 14
 <tr>
15 15
 	<td>open</td>
16
-	<td>/shop/checkout/delivery_address/</td>
16
+	<td>/shop/checkout/shipping-address/</td>
17 17
 	<td></td>
18 18
 </tr>
19 19
 <tr>
@@ -24,7 +24,7 @@
24 24
 <tr>
25 25
 	<td>type</td>
26 26
 	<td>id_last_name</td>
27
-	<td>Winterbottom</td>
27
+	<td>Winerbottom</td>
28 28
 </tr>
29 29
 <tr>
30 30
 	<td>type</td>
@@ -39,19 +39,18 @@
39 39
 <tr>
40 40
 	<td>type</td>
41 41
 	<td>id_postcode</td>
42
-	<td>N12 9ET</td>
42
+	<td>N12 9et</td>
43 43
 </tr>
44 44
 <tr>
45
-	<td>type</td>
45
+	<td>select</td>
46 46
 	<td>id_country</td>
47
-	<td>UK</td>
47
+	<td>label=United Kingdom of Great Britain and Northern Ireland</td>
48 48
 </tr>
49 49
 <tr>
50 50
 	<td>clickAndWait</td>
51
-	<td>//input[@value='Save delivery address']</td>
51
+	<td>//input[@value='Save shipping address']</td>
52 52
 	<td></td>
53 53
 </tr>
54
-
55 54
 </tbody></table>
56 55
 </body>
57 56
 </html>

+ 5
- 10
oscar/checkout/utils.py Parādīt failu

@@ -2,7 +2,7 @@ from django.core.urlresolvers import resolve
2 2
 
3 3
 from oscar.services import import_module
4 4
 
5
-shipping_models = import_module('shipping.models', ['Method'])
5
+shipping_methods = import_module('shipping.repository', ['Repository'])
6 6
 
7 7
 
8 8
 class ProgressChecker(object):
@@ -75,7 +75,6 @@ class ProgressChecker(object):
75 75
 class CheckoutSessionData(object):
76 76
     u"""Class responsible for marshalling all the checkout session data."""
77 77
     SESSION_KEY = 'checkout_data'
78
-    FREE_SHIPPING = '__free__'
79 78
     
80 79
     def __init__(self, request):
81 80
         self.request = request
@@ -122,9 +121,6 @@ class CheckoutSessionData(object):
122 121
     def user_address_id(self):
123 122
         return self._get('shipping', 'user_address_id')
124 123
     
125
-    def use_free_shipping(self):
126
-        self._set('shipping', 'method_code', '__free__')
127
-    
128 124
     def use_shipping_method(self, code):
129 125
         self._set('shipping', 'method_code', code)
130 126
         
@@ -134,8 +130,7 @@ class CheckoutSessionData(object):
134 130
         data stored in the session.
135 131
         """
136 132
         code = self._get('shipping', 'method_code')
137
-        if code == self.FREE_SHIPPING:
138
-            method = shipping_models.Method(name="Standard shipping (Free)", code=self.FREE_SHIPPING)
139
-        else:
140
-            method = shipping_models.Method.objects.get(code=code)
141
-        return method
133
+        if not code:
134
+            return None
135
+        repo = shipping_methods.Repository()
136
+        return repo.find_by_code(code)

+ 48
- 61
oscar/checkout/views.py Parādīt failu

@@ -1,3 +1,5 @@
1
+from decimal import Decimal
2
+
1 3
 from django.conf import settings
2 4
 from django.http import HttpResponse, Http404, HttpResponseRedirect, HttpResponseBadRequest
3 5
 from django.template import RequestContext
@@ -19,7 +21,7 @@ checkout_signals = import_module('checkout.signals', ['order_placed'])
19 21
 order_models = import_module('order.models', ['Order', 'ShippingAddress'])
20 22
 order_utils = import_module('order.utils', ['OrderCreator'])
21 23
 address_models = import_module('address.models', ['UserAddress'])
22
-shipping_models = import_module('shipping.models', ['Method'])
24
+shipping_repository = import_module('shipping.repository', ['Repository'])
23 25
 
24 26
 def prev_steps_must_be_complete(view_fn):
25 27
     u"""
@@ -78,6 +80,11 @@ class CheckoutView(object):
78 80
         # Set up the instance variables that are needed to place an order
79 81
         self.request = request
80 82
         self.co_data = checkout_utils.CheckoutSessionData(request)
83
+        self.basket = basket_factory.BasketFactory().get_open_basket(self.request)
84
+        self.context = {'basket': self.basket,
85
+                        'order_total': self.get_order_total(),
86
+                        'shipping_addr': self.get_shipping_address()}
87
+        self.set_shipping_context()
81 88
         
82 89
         if request.method == 'POST':
83 90
             response = self.handle_POST()
@@ -87,6 +94,14 @@ class CheckoutView(object):
87 94
             response = HttpResponseBadRequest()
88 95
         return response
89 96
     
97
+    def set_shipping_context(self):
98
+        method = self.co_data.shipping_method()
99
+        if method:
100
+            method.set_basket(self.basket)
101
+            self.context['method'] = method
102
+            self.context['shipping_total_excl_tax'] = method.basket_charge_excl_tax()
103
+            self.context['shipping_total_incl_tax'] = method.basket_charge_incl_tax()
104
+    
90 105
     def handle_GET(self):
91 106
         u"""
92 107
         Default behaviour is to set step as complete and redirect
@@ -94,6 +109,20 @@ class CheckoutView(object):
94 109
         """ 
95 110
         return self.get_success_response()
96 111
     
112
+    def get_order_total(self):
113
+        calc = checkout_calculators.OrderTotalCalculator(self.request)
114
+        return calc.order_total_incl_tax(self.basket)
115
+    
116
+    def get_shipping_address(self):
117
+        # Load address data into a blank address model
118
+        addr_data = self.co_data.new_address_fields()
119
+        if addr_data:
120
+            return order_models.ShippingAddress(**addr_data)
121
+        addr_id = self.co_data.user_address_id()
122
+        if addr_id:
123
+            return address_models.UserAddress.objects.get(pk=addr_id)
124
+        return None
125
+    
97 126
     def get_success_response(self):
98 127
         u"""
99 128
         Returns the appropriate redirect response if a checkout
@@ -146,19 +175,13 @@ class ShippingAddressView(CheckoutView):
146 175
                 form = checkout_forms.ShippingAddressForm(addr_fields)
147 176
             else:
148 177
                 form = checkout_forms.ShippingAddressForm()
178
+        self.context['form'] = form
149 179
     
150
-        # Add in extra template bindings
151
-        basket = basket_factory.BasketFactory().get_open_basket(self.request)
152
-        calc = checkout_calculators.OrderTotalCalculator(self.request)
153
-        order_total = calc.order_total_incl_tax(basket)
154
-        shipping_total_excl_tax = 0
155
-        shipping_total_incl_tax = 0
156
-        
157 180
         # Look up address book data
158 181
         if self.request.user.is_authenticated():
159
-            addresses = address_models.UserAddress.objects.filter(user=self.request.user)
182
+            self.context['addresses'] = address_models.UserAddress.objects.filter(user=self.request.user)
160 183
         
161
-        return render(self.request, self.template_file, locals())
184
+        return render(self.request, self.template_file, self.context)
162 185
     
163 186
     
164 187
 class ShippingMethodView(CheckoutView):
@@ -169,37 +192,22 @@ class ShippingMethodView(CheckoutView):
169 192
     template_file = 'checkout/shipping_methods.html';
170 193
     
171 194
     def handle_GET(self):
172
-        basket = basket_factory.BasketFactory().get_open_basket(self.request)
173
-        methods = self.get_shipping_methods_for_basket(basket)
174
-        
175
-        if not methods.count():
176
-            # No defined methods - assume delivery is free
177
-            self.co_data.use_free_shipping()
178
-            return self.get_success_response()
179
-        
180
-        if methods.count() == 1:
181
-            # Only one method - set this
195
+        methods = self.get_available_shipping_methods()
196
+        if len(methods) == 1:
197
+            # Only one method - set this and redirect onto the next step
182 198
             self.co_data.use_shipping_method(methods[0].code)
183 199
             return self.get_success_response()
184 200
         
185
-        for method in methods:
186
-            method.set_basket(basket)
187
-        
188
-        # Load address data into a blank address model
189
-        addr_data = self.co_data.new_address_fields()
190
-        if addr_data:
191
-            shipping_addr = order_models.ShippingAddress(**addr_data)
192
-        addr_id = self.co_data.user_address_id()
193
-        if addr_id:
194
-            shipping_addr = address_models.UserAddress.objects.get(pk=addr_id)
195
-        
196
-        calc = checkout_calculators.OrderTotalCalculator(self.request)
197
-        order_total = calc.order_total_incl_tax(basket)
198
-        
199
-        return render(self.request, self.template_file, locals())
201
+        self.context['methods'] = methods
202
+        return render(self.request, self.template_file, self.context)
200 203
     
201
-    def get_shipping_methods_for_basket(self, basket):
202
-        return shipping_models.Method.objects.all()
204
+    def get_available_shipping_methods(self):
205
+        u"""
206
+        Returns all applicable shipping method objects
207
+        for a given basket.
208
+        """ 
209
+        repo = shipping_repository.Repository()
210
+        return repo.get_shipping_methods(self.request.user, self.basket, self.get_shipping_address())
203 211
     
204 212
     def handle_POST(self):
205 213
         method_code = self.request.POST['method_code']
@@ -224,29 +232,9 @@ class OrderPreviewView(CheckoutView):
224 232
     template_file = 'checkout/preview.html'
225 233
     
226 234
     def handle_GET(self):
227
-        basket = basket_factory.BasketFactory().get_open_basket(self.request)
228
-        
229
-        # Load address data into a blank address model
230
-        addr_data = self.co_data.new_address_fields()
231
-        if addr_data:
232
-            shipping_addr = order_models.ShippingAddress(**addr_data)
233
-        addr_id = self.co_data.user_address_id()
234
-        if addr_id:
235
-            shipping_addr = address_models.UserAddress.objects.get(pk=addr_id)
236
-        
237
-        # Shipping method
238
-        method = self.co_data.shipping_method()
239
-        method.set_basket(basket)
240
-
241
-        shipping_total_excl_tax = method.basket_charge_excl_tax()
242
-        shipping_total_incl_tax = method.basket_charge_incl_tax()
243
-        
244
-        # Calculate order total
245
-        calc = checkout_calculators.OrderTotalCalculator(self.request)
246
-        order_total = calc.order_total_incl_tax(basket, method)
247 235
         
248 236
         mark_step_as_complete(self.request)
249
-        return render(self.request, self.template_file, locals())
237
+        return render(self.request, self.template_file, self.context)
250 238
 
251 239
 
252 240
 class PaymentDetailsView(CheckoutView):
@@ -277,9 +265,8 @@ class SubmitView(CheckoutView):
277 265
     
278 266
     def handle_POST(self):
279 267
         
280
-        basket = basket_factory.BasketFactory().get_open_basket(self.request)
281
-        self._handle_payment(basket)
282
-        order = self._place_order(basket)
268
+        self._handle_payment(self.basket)
269
+        order = self._place_order(self.basket)
283 270
         self._reset_checkout()
284 271
         
285 272
         # Send signal

+ 14
- 3
oscar/shipping/abstract_models.py Parādīt failu

@@ -1,13 +1,24 @@
1
-import zlib
2 1
 from decimal import Decimal
3 2
 
4 3
 from django.db import models
5 4
 from django.utils.translation import ugettext_lazy as _
6 5
 from django.template.defaultfilters import slugify
7 6
 
7
+from oscar.shipping.methods import ShippingMethod
8 8
 
9
-class AbstractMethod(models.Model):
10
-    u"""Shipping method"""
9
+
10
+class AbstractMethod(models.Model, ShippingMethod):
11
+    u"""
12
+    Standard shipping method
13
+    
14
+    This method has two components: 
15
+    * a charge per order
16
+    * a charge per item
17
+    
18
+    Many sites use shipping logic which fits into this system.  However, for more
19
+    complex shipping logic, a custom shipping method object will need to be provided
20
+    that subclasses ShippingMethod.
21
+    """
11 22
     code = models.CharField(max_length=128, unique=True)
12 23
     name = models.CharField(_("Name"), max_length=128)
13 24
     description = models.TextField(_("Description"), blank=True)

+ 31
- 0
oscar/shipping/methods.py Parādīt failu

@@ -0,0 +1,31 @@
1
+class ShippingMethod(object):
2
+    u"""
3
+    Superclass for all shipping method objects
4
+    """
5
+    code = '__default__'
6
+    name = 'Default shipping'
7
+    description = ''
8
+    
9
+    def set_basket(self, basket):
10
+        self.basket = basket
11
+    
12
+    def basket_charge_incl_tax(self):
13
+        pass
14
+    
15
+    def basket_charge_excl_tax(self):
16
+        pass
17
+    
18
+    
19
+class FreeShipping(ShippingMethod):
20
+    u"""
21
+    Simple method for free shipping
22
+    """
23
+    code = 'free-shipping'
24
+    name = 'Free shipping'
25
+    
26
+    def basket_charge_incl_tax(self):
27
+        return Decimal('0.00')
28
+    
29
+    def basket_charge_excl_tax(self):
30
+        return Decimal('0.00')
31
+       

+ 36
- 0
oscar/shipping/repository.py Parādīt failu

@@ -0,0 +1,36 @@
1
+from oscar.shipping.methods import FreeShipping
2
+from oscar.services import import_module
3
+
4
+shipping_models = import_module('shipping.models', ['Method'])
5
+
6
+
7
+class Repository(object):
8
+    u"""
9
+    Repository class responsible for returning ShippingMethod
10
+    objects
11
+    """
12
+    
13
+    def get_shipping_methods(self, user, basket, shipping_addr):
14
+        u"""
15
+        Returns all applicable shipping method objects
16
+        for a given basket.
17
+        
18
+        We default to returning the Method models that have been defined but
19
+        this behaviour can easily be overridden by subclassing this class
20
+        and overriding this method.
21
+        """ 
22
+        methods = shipping_models.Method.objects.all()
23
+        if not methods.count():
24
+            return [FreeShipping()]
25
+        
26
+        for method in methods:
27
+            method.set_basket(basket)
28
+        return methods
29
+
30
+    def find_by_code(self, code):
31
+        u"""
32
+        Returns the appropriate Method object for the given code
33
+        """
34
+        if code == FreeShipping.code:
35
+            return FreeShipping()
36
+        return shipping_models.Method.objects.get(code=code)          

+ 3
- 1
oscar/templates/checkout/shipping_address.html Parādīt failu

@@ -77,13 +77,15 @@
77 77
         <td>{{ basket.total_excl_tax|currency }}</td>
78 78
         <td>{{ basket.total_incl_tax|currency }}</td>
79 79
     </tr>
80
+    {% if shipping_total_excl_tax %}
80 81
     <tr>
81 82
         <td colspan="5">shipping charge</td>
82 83
         <td>{{ shipping_total_excl_tax|currency }}</td>
83 84
         <td>{{ shipping_total_incl_tax|currency }}</td>
84 85
     </tr>
86
+    {% endif %}
85 87
     <tr>
86
-        <td colspan="6">Order total</td>
88
+        <td colspan="6">Order total (before shipping)</td>
87 89
         <td>{{ order_total|currency }}</td>
88 90
     </tr>
89 91
 </table>

Notiek ielāde…
Atcelt
Saglabāt