Explorar el Código

Merge branch 'master' into offer-proxy-models

Conflicts:
	docs/source/index.rst
	oscar/order/utils.py
master
David Winterbottom hace 15 años
padre
commit
f97c2d43ed

+ 1
- 6
docs/source/contributing.rst Ver fichero

@@ -10,12 +10,6 @@ Guidelines
10 10
 * Pull requests will be rejected if sufficient tests aren't provided
11 11
 * Please updated the documentation when altering behaviour or introducing new features 
12 12
 
13
-Coding conventions
14
-------------------
15
-
16
-* Follow the Django conventions (http://docs.djangoproject.com/en/dev/internals/contributing/#coding-style)
17
-* PEP8 (http://www.python.org/dev/peps/pep-0008/)
18
-* PEP257 (http://www.python.org/dev/peps/pep-0257/)
19 13
 
20 14
 Contents:
21 15
 
@@ -24,3 +18,4 @@ Contents:
24 18
 
25 19
    contributing/installation
26 20
    contributing/testing
21
+   contributing/conventions

+ 22
- 0
docs/source/contributing/conventions.rst Ver fichero

@@ -0,0 +1,22 @@
1
+===========
2
+Conventions
3
+===========
4
+
5
+Coding conventions
6
+------------------
7
+
8
+* Follow the Django conventions (http://docs.djangoproject.com/en/dev/internals/contributing/#coding-style)
9
+* PEP8 (http://www.python.org/dev/peps/pep-0008/)
10
+* PEP257 (http://www.python.org/dev/peps/pep-0257/)
11
+
12
+General guidelines
13
+------------------
14
+
15
+The following are a set of design conventions that should be followed when
16
+writing apps for oscar:
17
+
18
+* When referencing managers of model classes, use ``_default_manager`` rather than
19
+  ``objects``.  This allows projects to override the default manager to provide
20
+  domain-specific behaviour.
21
+
22
+

+ 13
- 1
docs/source/getting_started/installation.rst Ver fichero

@@ -62,4 +62,16 @@ A vanilla install of django-oscar is now ready, you could now finish the process
62 62
     ./manage.py syncdb
63 63
 
64 64
 However, in reality you will need to start extending the models to match your domain.  It's best to do
65
-this before creating your initial schema.
65
+this before creating your initial schema.
66
+
67
+Configure urls
68
+--------------
69
+
70
+Oscar comes with a number of urls and views out of the box.  These are
71
+recommendations rather than a requirement but you easily use them in your
72
+e-commerce site by adding the oscar urls to your projects local ``urls.py``
73
+
74
+    (r'^', include('oscar.urls')),
75
+
76
+This will bring in all of oscar's defined urls. Of course you can pull in the
77
+urls for the individual apps if you prefer or simply define your own

+ 1
- 1
docs/source/index.rst Ver fichero

@@ -32,7 +32,7 @@ Contents:
32 32
    web_services
33 33
    recipes
34 34
    contributing
35
-   settings
35
+   reference
36 36
 
37 37
 Indices and tables
38 38
 ==================

+ 10
- 0
docs/source/reference.rst Ver fichero

@@ -0,0 +1,10 @@
1
+=========
2
+Reference
3
+=========
4
+
5
+Contents:
6
+
7
+.. toctree::
8
+   :maxdepth: 2
9
+
10
+   reference/settings

+ 31
- 0
docs/source/reference/settings.rst Ver fichero

@@ -0,0 +1,31 @@
1
+=======================
2
+Oscar specific settings
3
+=======================
4
+
5
+Oscar provides a number of configurable settings used to confugre the system.
6
+
7
+.. contents::
8
+    :local:
9
+    :depth: 1
10
+
11
+Available settings
12
+==================
13
+
14
+OSCAR_DEFAULT_CURRENCY
15
+----------------------
16
+
17
+Default: None (This is a required field)
18
+
19
+This should be the symbol of the currency you wish Oscar to use by default.
20
+
21
+OSCAR_BASKET_COOKIE_LIFETIME
22
+----------------------------
23
+
24
+Default: 604800 (1 week in seconds)
25
+
26
+The time to live for the basket cookie in seconds
27
+
28
+Deprecated settings
29
+===================
30
+
31
+There are currently no deprecated settings in oscar

+ 0
- 7
docs/source/settings.rst Ver fichero

@@ -1,7 +0,0 @@
1
-Settings
2
-========
3
-
4
-The following settings can be configured in your `settings.py`:
5
-
6
-* `OSCAR_DEFAULT_CURRENCY` - The default currency for all price fields within models
7
-* `OSCAR_BASKET_COOKIE_LIFETIME` - The lifespan of a basket cookie in seconds - defaults to a week.

+ 138
- 0
oscar/checkout/core_views.py Ver fichero

@@ -0,0 +1,138 @@
1
+from django.http import HttpResponse, Http404, HttpResponseRedirect, HttpResponseBadRequest
2
+from django.contrib import messages
3
+from django.core.urlresolvers import reverse
4
+
5
+from oscar.services import import_module
6
+basket_factory = import_module('basket.factory', ['BasketFactory'])
7
+checkout_calculators = import_module('checkout.calculators', ['OrderTotalCalculator'])
8
+order_models = import_module('order.models', ['Order', 'ShippingAddress'])
9
+checkout_utils = import_module('checkout.utils', ['ProgressChecker', 'CheckoutSessionData'])
10
+address_models = import_module('address.models', ['UserAddress'])
11
+
12
+def prev_steps_must_be_complete(view_fn):
13
+    u"""
14
+    Decorator fn for checking that previous steps of the checkout
15
+    are complete.
16
+    
17
+    The completed steps (identified by URL-names) are stored in the session.
18
+    If this fails, then we redirect to the next uncompleted step.
19
+    """
20
+    def _view_wrapper(self, request, *args, **kwargs):
21
+        checker = checkout_utils.ProgressChecker()
22
+        if not checker.are_previous_steps_complete(request):
23
+            messages.error(request, "You must complete this step of the checkout first")
24
+            url_name = checker.get_next_step(request)
25
+            return HttpResponseRedirect(reverse(url_name))
26
+        return view_fn(self, request, *args, **kwargs)
27
+    return _view_wrapper
28
+
29
+def basket_required(view_fn):
30
+    def _view_wrapper(self, request, *args, **kwargs):
31
+        basket = basket_factory.BasketFactory().get_open_basket(request)
32
+        if not basket:
33
+            messages.error(request, "You must add some products to your basket before checking out")
34
+            return HttpResponseRedirect(reverse('oscar-basket'))
35
+        return view_fn(self, request, *args, **kwargs)
36
+    return _view_wrapper
37
+
38
+def mark_step_as_complete(request):
39
+    u""" 
40
+    Convenience function for marking a checkout page
41
+    as complete.
42
+    """
43
+    checkout_utils.ProgressChecker().step_complete(request)
44
+    
45
+def mark_step_as_complete(request):
46
+    u""" 
47
+    Convenience function for marking a checkout page
48
+    as complete.
49
+    """
50
+    checkout_utils.ProgressChecker().step_complete(request)
51
+    
52
+def get_next_step(request):
53
+    return checkout_utils.ProgressChecker().get_next_step(request)    
54
+    
55
+
56
+class CheckoutView(object):
57
+    u"""
58
+    Top-level superclass for all checkout view classes
59
+    """
60
+    
61
+    def __init__(self, template_file=None):
62
+        if template_file:
63
+            self.template_file = template_file
64
+        
65
+    @basket_required
66
+    @prev_steps_must_be_complete
67
+    def __call__(self, request):
68
+        u"""
69
+        We forward request handling to the appropriate method
70
+        based on the HTTP method.
71
+        """
72
+        
73
+        # Set up the instance variables that are needed to place an order
74
+        self.request = request
75
+        self.co_data = checkout_utils.CheckoutSessionData(request)
76
+        self.basket = basket_factory.BasketFactory().get_open_basket(self.request)
77
+        
78
+        # Set up template context that is available to every view
79
+        method = self.co_data.shipping_method()
80
+        if method:
81
+            method.set_basket(self.basket)
82
+        
83
+        self.context = {'basket': self.basket,
84
+                        'order_total': self.get_order_total(method),
85
+                        'shipping_addr': self.get_shipping_address()}
86
+        self.set_shipping_context(method)
87
+        self.set_payment_context()
88
+        
89
+        if request.method == 'POST':
90
+            response = self.handle_POST()
91
+        elif request.method == 'GET':
92
+            response = self.handle_GET()
93
+        else:
94
+            response = HttpResponseBadRequest()
95
+        return response
96
+    
97
+    def set_shipping_context(self, method):
98
+        if method:
99
+            self.context['method'] = method
100
+            self.context['shipping_total_excl_tax'] = method.basket_charge_excl_tax()
101
+            self.context['shipping_total_incl_tax'] = method.basket_charge_incl_tax()
102
+            
103
+    def set_payment_context(self):
104
+        method = self.co_data.payment_method()
105
+        if method:
106
+            self.context['payment_method'] = method
107
+    
108
+    def handle_GET(self):
109
+        u"""
110
+        Default behaviour is to set step as complete and redirect
111
+        to the next step.
112
+        """ 
113
+        return self.get_success_response()
114
+    
115
+    def get_order_total(self, shipping_method):
116
+        calc = checkout_calculators.OrderTotalCalculator(self.request)
117
+        return calc.order_total_incl_tax(self.basket, shipping_method)
118
+    
119
+    def get_shipping_address(self):
120
+        # Load address data into a blank address model
121
+        addr_data = self.co_data.new_address_fields()
122
+        if addr_data:
123
+            return order_models.ShippingAddress(**addr_data)
124
+        addr_id = self.co_data.user_address_id()
125
+        if addr_id:
126
+            return address_models.UserAddress._default_manager.get(pk=addr_id)
127
+        return None
128
+    
129
+    def get_success_response(self):
130
+        u"""
131
+        Returns the appropriate redirect response if a checkout
132
+        step has been successfully passed.
133
+        """
134
+        mark_step_as_complete(self.request)
135
+        return HttpResponseRedirect(reverse(get_next_step(self.request)))
136
+    
137
+    def handle_POST(self):
138
+        pass

+ 1
- 1
oscar/checkout/forms.py Ver fichero

@@ -13,7 +13,7 @@ class ShippingAddressForm(ModelForm):
13 13
         self.set_country_queryset() 
14 14
         
15 15
     def set_country_queryset(self):    
16
-        self.fields['country'].queryset = address_models.Country.objects.filter(is_shipping_country=True)
16
+        self.fields['country'].queryset = address_models.Country._default_manager.filter(is_shipping_country=True)
17 17
     
18 18
     class Meta:
19 19
         model = order_models.ShippingAddress

+ 12
- 131
oscar/checkout/views.py Ver fichero

@@ -18,131 +18,12 @@ checkout_forms = import_module('checkout.forms', ['ShippingAddressForm'])
18 18
 checkout_calculators = import_module('checkout.calculators', ['OrderTotalCalculator'])
19 19
 checkout_utils = import_module('checkout.utils', ['ProgressChecker', 'CheckoutSessionData'])
20 20
 checkout_signals = import_module('checkout.signals', ['order_placed', 'pre_payment', 'post_payment'])
21
+checkout_views = import_module('checkout.core_views', ['CheckoutView', 'mark_step_as_complete'])
21 22
 order_models = import_module('order.models', ['Order', 'ShippingAddress'])
22 23
 order_utils = import_module('order.utils', ['OrderNumberGenerator', 'OrderCreator'])
23 24
 address_models = import_module('address.models', ['UserAddress'])
24 25
 shipping_repository = import_module('shipping.repository', ['Repository'])
25 26
 
26
-def prev_steps_must_be_complete(view_fn):
27
-    u"""
28
-    Decorator fn for checking that previous steps of the checkout
29
-    are complete.
30
-    
31
-    The completed steps (identified by URL-names) are stored in the session.
32
-    If this fails, then we redirect to the next uncompleted step.
33
-    """
34
-    def _view_wrapper(self, request, *args, **kwargs):
35
-        checker = checkout_utils.ProgressChecker()
36
-        if not checker.are_previous_steps_complete(request):
37
-            messages.error(request, "You must complete this step of the checkout first")
38
-            url_name = checker.get_next_step(request)
39
-            assert False
40
-            return HttpResponseRedirect(reverse(url_name))
41
-        return view_fn(self, request, *args, **kwargs)
42
-    return _view_wrapper
43
-
44
-def basket_required(view_fn):
45
-    def _view_wrapper(self, request, *args, **kwargs):
46
-        basket = basket_factory.BasketFactory().get_open_basket(request)
47
-        if not basket:
48
-            messages.error(request, "You must add some products to your basket before checking out")
49
-            return HttpResponseRedirect(reverse('oscar-basket'))
50
-        return view_fn(self, request, *args, **kwargs)
51
-    return _view_wrapper
52
-
53
-def mark_step_as_complete(request):
54
-    u""" 
55
-    Convenience function for marking a checkout page
56
-    as complete.
57
-    """
58
-    checkout_utils.ProgressChecker().step_complete(request)
59
-    
60
-def get_next_step(request):
61
-    return checkout_utils.ProgressChecker().get_next_step(request)
62
-
63
-
64
-class CheckoutView(object):
65
-    u"""
66
-    Top-level superclass for all checkout view classes
67
-    """
68
-    
69
-    def __init__(self, template_file=None):
70
-        if template_file:
71
-            self.template_file = template_file
72
-        
73
-    @basket_required
74
-    @prev_steps_must_be_complete
75
-    def __call__(self, request):
76
-        u"""
77
-        We forward request handling to the appropriate method
78
-        based on the HTTP method.
79
-        """
80
-        
81
-        # Set up the instance variables that are needed to place an order
82
-        self.request = request
83
-        self.co_data = checkout_utils.CheckoutSessionData(request)
84
-        self.basket = basket_factory.BasketFactory().get_open_basket(self.request)
85
-        
86
-        # Set up template context that is available to every view
87
-        self.context = {'basket': self.basket,
88
-                        'order_total': self.get_order_total(),
89
-                        'shipping_addr': self.get_shipping_address()}
90
-        self.set_shipping_context()
91
-        self.set_payment_context()
92
-        
93
-        if request.method == 'POST':
94
-            response = self.handle_POST()
95
-        elif request.method == 'GET':
96
-            response = self.handle_GET()
97
-        else:
98
-            response = HttpResponseBadRequest()
99
-        return response
100
-    
101
-    def set_shipping_context(self):
102
-        method = self.co_data.shipping_method()
103
-        if method:
104
-            method.set_basket(self.basket)
105
-            self.context['method'] = method
106
-            self.context['shipping_total_excl_tax'] = method.basket_charge_excl_tax()
107
-            self.context['shipping_total_incl_tax'] = method.basket_charge_incl_tax()
108
-            
109
-    def set_payment_context(self):
110
-        method = self.co_data.payment_method()
111
-        if method:
112
-            self.context['payment_method'] = method
113
-    
114
-    def handle_GET(self):
115
-        u"""
116
-        Default behaviour is to set step as complete and redirect
117
-        to the next step.
118
-        """ 
119
-        return self.get_success_response()
120
-    
121
-    def get_order_total(self):
122
-        calc = checkout_calculators.OrderTotalCalculator(self.request)
123
-        return calc.order_total_incl_tax(self.basket)
124
-    
125
-    def get_shipping_address(self):
126
-        # Load address data into a blank address model
127
-        addr_data = self.co_data.new_address_fields()
128
-        if addr_data:
129
-            return order_models.ShippingAddress(**addr_data)
130
-        addr_id = self.co_data.user_address_id()
131
-        if addr_id:
132
-            return address_models.UserAddress.objects.get(pk=addr_id)
133
-        return None
134
-    
135
-    def get_success_response(self):
136
-        u"""
137
-        Returns the appropriate redirect response if a checkout
138
-        step has been successfully passed.
139
-        """
140
-        mark_step_as_complete(self.request)
141
-        return HttpResponseRedirect(reverse(get_next_step(self.request)))
142
-    
143
-    def handle_POST(self):
144
-        pass
145
-
146 27
 
147 28
 class IndexView(object):
148 29
     template_file = 'checkout/gateway.html'
@@ -153,12 +34,12 @@ class IndexView(object):
153 34
         return render(request, self.template_file, locals())    
154 35
 
155 36
 
156
-class ShippingAddressView(CheckoutView):
37
+class ShippingAddressView(checkout_views.CheckoutView):
157 38
     template_file = 'checkout/shipping_address.html'
158 39
     
159 40
     def handle_POST(self):
160 41
         if self.request.user.is_authenticated and 'address_id' in self.request.POST:
161
-            address = address_models.UserAddress.objects.get(pk=self.request.POST['address_id'])
42
+            address = address_models.UserAddress._default_manager.get(pk=self.request.POST['address_id'])
162 43
             if 'action' in self.request.POST and self.request.POST['action'] == 'ship_to':
163 44
                 # User has selected a previous address to ship to
164 45
                 self.co_data.ship_to_user_address(address)
@@ -188,12 +69,12 @@ class ShippingAddressView(CheckoutView):
188 69
     
189 70
         # Look up address book data
190 71
         if self.request.user.is_authenticated():
191
-            self.context['addresses'] = address_models.UserAddress.objects.filter(user=self.request.user)
72
+            self.context['addresses'] = address_models.UserAddress._default_manager.filter(user=self.request.user)
192 73
         
193 74
         return render(self.request, self.template_file, self.context)
194 75
     
195 76
     
196
-class ShippingMethodView(CheckoutView):
77
+class ShippingMethodView(checkout_views.CheckoutView):
197 78
     u"""
198 79
     Shipping methods are domain-specific and so need implementing in a 
199 80
     subclass of this class.
@@ -224,7 +105,7 @@ class ShippingMethodView(CheckoutView):
224 105
         return self.get_success_response()
225 106
         
226 107
 
227
-class PaymentMethodView(CheckoutView):
108
+class PaymentMethodView(checkout_views.CheckoutView):
228 109
     u"""
229 110
     View for a user to choose which payment method(s) they want to use.
230 111
     
@@ -234,17 +115,17 @@ class PaymentMethodView(CheckoutView):
234 115
     pass
235 116
 
236 117
 
237
-class OrderPreviewView(CheckoutView):
118
+class OrderPreviewView(checkout_views.CheckoutView):
238 119
     u"""View a preview of the order before submitting."""
239 120
     
240 121
     template_file = 'checkout/preview.html'
241 122
     
242 123
     def handle_GET(self):
243
-        mark_step_as_complete(self.request)
124
+        checkout_views.mark_step_as_complete(self.request)
244 125
         return render(self.request, self.template_file, self.context)
245 126
 
246 127
 
247
-class PaymentDetailsView(CheckoutView):
128
+class PaymentDetailsView(checkout_views.CheckoutView):
248 129
     u"""
249 130
     For taking the details of payment and creating the order
250 131
     
@@ -359,13 +240,13 @@ class PaymentDetailsView(CheckoutView):
359 240
             # Check that this address isn't already in the db as we don't want
360 241
             # to fill up the customer address book with duplicate addresses
361 242
             try:
362
-                duplicate_addr = address_models.UserAddress.objects.get(hash=user_addr.generate_hash())
243
+                duplicate_addr = address_models.UserAddress._default_manager.get(hash=user_addr.generate_hash())
363 244
             except ObjectDoesNotExist:
364 245
                 user_addr.save()
365 246
     
366 247
     def _create_shipping_address_from_user_address(self, addr_id):
367 248
         u"""Creates a shipping address from a user address"""
368
-        address = address_models.UserAddress.objects.get(pk=addr_id)
249
+        address = address_models.UserAddress._default_manager.get(pk=addr_id)
369 250
         # Increment the number of orders to help determine popularity of orders 
370 251
         address.num_orders += 1
371 252
         address.save()
@@ -380,7 +261,7 @@ class ThankYouView(object):
380 261
     
381 262
     def __call__(self, request):
382 263
         try:
383
-            order = order_models.Order.objects.get(pk=request.session['checkout_order_id'])
264
+            order = order_models.Order._default_manager.get(pk=request.session['checkout_order_id'])
384 265
             
385 266
             # Remove order number from session to ensure that the thank-you page is only 
386 267
             # viewable once.

+ 3
- 3
oscar/customer/views.py Ver fichero

@@ -14,7 +14,7 @@ order_models = import_module('order.models', ['Order'])
14 14
 def profile(request):
15 15
     u"""Return a customers's profile"""
16 16
     # Load last 5 orders as preview
17
-    orders = order_models.Order.objects.filter(user=request.user)[0:5]
17
+    orders = order_models.Order._default_manager.filter(user=request.user)[0:5]
18 18
     return render(request, 'customer/profile.html', locals())
19 19
     
20 20
         
@@ -26,7 +26,7 @@ class OrderHistoryView(ListView):
26 26
 
27 27
     def get_queryset(self):
28 28
         u"""Return a customer's orders"""
29
-        return order_models.Order.objects.filter(user=self.request.user)
29
+        return order_models.Order._default_manager.filter(user=self.request.user)
30 30
 
31 31
 
32 32
 class OrderDetailView(ModelView):
@@ -49,7 +49,7 @@ class AddressBookView(ListView):
49 49
         
50 50
     def get_queryset(self):
51 51
         u"""Return a customer's addresses"""
52
-        return address_models.UserAddress.objects.filter(user=self.request.user)
52
+        return address_models.UserAddress._default_manager.filter(user=self.request.user)
53 53
     
54 54
     
55 55
 class AddressView(ModelView):

+ 18
- 7
oscar/order/abstract_models.py Ver fichero

@@ -136,18 +136,29 @@ class AbstractLine(models.Model):
136 136
     information when it splits across a line.
137 137
     """
138 138
     order = models.ForeignKey('order.Order', related_name='lines')
139
+    
140
+    # We store the partner and their SKU for cases where the product has been
141
+    # deleted from the catalogue.
139 142
     partner = models.ForeignKey('stock.Partner', related_name='order_lines')
140
-    product = models.ForeignKey('product.Item')
143
+    partner_reference = models.CharField(_("Partner reference"), max_length=128, blank=True, null=True)
144
+    
145
+    # We don't want any hard links between orders and the products table
146
+    product = models.ForeignKey('product.Item', on_delete=models.SET_NULL, null=True)
141 147
     quantity = models.PositiveIntegerField(default=1)
148
+    
142 149
     # Price information (these fields are actually redundant as the information
143 150
     # can be calculated from the LinePrice models
144 151
     line_price_incl_tax = models.DecimalField(decimal_places=2, max_digits=12)
145 152
     line_price_excl_tax = models.DecimalField(decimal_places=2, max_digits=12)
146 153
     
154
+    # Cost price (the price charged by the fulfilment partner for this product).  This
155
+    # is useful for audit and financial reporting.
156
+    cost_price = models.DecimalField(decimal_places=2, max_digits=12, blank=True, null=True)
157
+    
147 158
     # Partner information
148
-    partner_reference = models.CharField(_("Partner reference"), max_length=128, blank=True, null=True,
159
+    partner_line_reference = models.CharField(_("Partner reference"), max_length=128, blank=True, null=True,
149 160
         help_text=_("This is the item number that the partner uses within their system"))
150
-    partner_notes = models.TextField(blank=True, null=True)
161
+    partner_line_notes = models.TextField(blank=True, null=True)
151 162
     
152 163
     @property
153 164
     def description(self):
@@ -324,16 +335,16 @@ class ShippingEventQuantity(models.Model):
324 335
 
325 336
     def _check_previous_events_are_complete(self):
326 337
         u"""Checks whether previous shipping events have passed"""
327
-        previous_events = ShippingEventQuantity.objects.filter(line=self.line, 
328
-                                                               event__event_type__sequence_number__lt=self.event.event_type.sequence_number)
338
+        previous_events = ShippingEventQuantity._default_manager.filter(line=self.line, 
339
+                                                                        event__event_type__sequence_number__lt=self.event.event_type.sequence_number)
329 340
         self.quantity = int(self.quantity)
330 341
         for event_quantities in previous_events:
331 342
             if event_quantities.quantity < self.quantity:
332 343
                 raise ValueError("Invalid quantity (%d) for event type (a previous event has not been fully passed)" % self.quantity)
333 344
 
334 345
     def _check_new_quantity(self):
335
-        quantity_row = ShippingEventQuantity.objects.filter(line=self.line, 
336
-                                                            event__event_type=self.event.event_type).aggregate(Sum('quantity'))
346
+        quantity_row = ShippingEventQuantity._default_manager.filter(line=self.line, 
347
+                                                                     event__event_type=self.event.event_type).aggregate(Sum('quantity'))
337 348
         previous_quantity = quantity_row['quantity__sum']
338 349
         if previous_quantity == None:
339 350
             previous_quantity = 0

+ 2
- 2
oscar/order/fixtures/sample-order.json Ver fichero

@@ -39,7 +39,7 @@
39 39
         "model": "order.line", 
40 40
         "fields": {
41 41
 			"partner": 1,
42
-            "partner_notes": null, 
42
+            "partner_line_notes": null, 
43 43
             "product": 8, 
44 44
             "line_price_excl_tax": "69.90", 
45 45
             "partner_reference": null, 
@@ -53,7 +53,7 @@
53 53
         "model": "order.line", 
54 54
         "fields": {
55 55
 			"partner": 1,
56
-            "partner_notes": null, 
56
+            "partner_line_notes": null, 
57 57
             "product": 9, 
58 58
             "line_price_excl_tax": "68.80", 
59 59
             "partner_reference": null, 

+ 12
- 9
oscar/order/utils.py Ver fichero

@@ -47,7 +47,7 @@ class OrderCreator(object):
47 47
         calc = self.order_total_calculator
48 48
         order_data = {'basket': basket,
49 49
                       'number': order_number,
50
-                      'site': Site.objects.get_current(),
50
+                      'site': Site._default_manager.get_current(),
51 51
                       'total_incl_tax': calc.order_total_incl_tax(basket, shipping_method),
52 52
                       'total_excl_tax': calc.order_total_excl_tax(basket, shipping_method),
53 53
                       'shipping_address': shipping_address,
@@ -68,12 +68,15 @@ class OrderCreator(object):
68 68
     
69 69
     def _create_line_models(self, order, basket_line):
70 70
         u"""Creates the batch line model."""
71
-        order_line = order_models.Line.objects.create(order=order,
72
-                                                      partner=self._get_partner_for_product(basket_line.product),
73
-                                                      product=basket_line.product, 
74
-                                                      quantity=basket_line.quantity, 
75
-                                                      line_price_excl_tax=basket_line.line_price_excl_tax_and_discounts, 
76
-                                                      line_price_incl_tax=basket_line.line_price_incl_tax_and_discounts)
71
+        order_line = order_models.Line(order=order,
72
+                                      partner=self._get_partner_for_product(basket_line.product),
73
+                                      product=basket_line.product, 
74
+                                      quantity=basket_line.quantity, 
75
+                                      line_price_excl_tax=basket_line.line_price_excl_tax_and_discounts, 
76
+                                      line_price_incl_tax=basket_line.line_price_incl_tax_and_discounts)
77
+        if basket_line.product.has_stockrecord:
78
+            order_line.partner_reference = basket_line.product.stockrecord.partner_reference
79
+        order_line.save()
77 80
         self._create_line_price_models(order, order_line, basket_line)
78 81
         self._create_line_attributes(order, order_line, basket_line)
79 82
         
@@ -81,7 +84,7 @@ class OrderCreator(object):
81 84
         u"""Creates the batch line price models"""
82 85
         breakdown = basket_line.get_price_breakdown()
83 86
         for price_incl_tax, price_excl_tax, quantity in breakdown:
84
-            order_models.LinePrice.objects.create(order=order,
87
+            order_models.LinePrice._default_manager.create(order=order,
85 88
                                                   line=order_line, 
86 89
                                                   quantity=quantity, 
87 90
                                                   price_incl_tax=price_incl_tax,
@@ -90,5 +93,5 @@ class OrderCreator(object):
90 93
     def _create_line_attributes(self, order, order_line, basket_line):
91 94
         u"""Creates the batch line attributes."""
92 95
         for attr in basket_line.attributes.all():
93
-            order_models.LineAttribute.objects.create(line=order_line, type=attr.option.code,
96
+            order_models.LineAttribute._default_manager.create(line=order_line, type=attr.option.code,
94 97
                                                       value=attr.value)

+ 9
- 9
oscar/order_management/views.py Ver fichero

@@ -20,7 +20,7 @@ class OrderListView(ListView):
20 20
     paginate_by = 20
21 21
 
22 22
     def get_queryset(self):
23
-        return order_models.Order.objects.all()
23
+        return order_models.Order._default_manager.all()
24 24
     
25 25
   
26 26
 class OrderView(ModelView):
@@ -32,8 +32,8 @@ class OrderView(ModelView):
32 32
         return get_object_or_404(order_models.Order, number=self.kwargs['order_number'])
33 33
     
34 34
     def handle_GET(self, order):
35
-        shipping_options = order_models.ShippingEventType.objects.all()
36
-        payment_options = order_models.PaymentEventType.objects.all()
35
+        shipping_options = order_models.ShippingEventType._default_manager.all()
36
+        payment_options = order_models.PaymentEventType._default_manager.all()
37 37
         
38 38
         self.response = render(self.request, self.template_file, locals())
39 39
         
@@ -60,21 +60,21 @@ class OrderView(ModelView):
60 60
     def create_shipping_event(self, order, lines):
61 61
         u"""Create a shipping event for an order"""
62 62
         with transaction.commit_on_success():
63
-            event_type = order_models.ShippingEventType.objects.get(code=self.request.POST['shipping_event'])
64
-            event = order_models.ShippingEvent.objects.create(order=order, event_type=event_type)
63
+            event_type = order_models.ShippingEventType._default_manager.get(code=self.request.POST['shipping_event'])
64
+            event = order_models.ShippingEvent._default_manager.create(order=order, event_type=event_type)
65 65
             for line in lines:
66 66
                 try:
67 67
                     event_quantity = int(self.request.POST['order_line_quantity_%d' % line.id])
68 68
                 except KeyError:
69 69
                     event_quantity = line.quantity
70
-                order_models.ShippingEventQuantity.objects.create(event=event, line=line, 
70
+                order_models.ShippingEventQuantity._default_manager.create(event=event, line=line, 
71 71
                                                                   quantity=event_quantity)
72 72
             
73 73
     def create_payment_event(self, order, lines, type_code):
74 74
         u"""Create a payment event for an order"""
75
-        event_type = order_models.PaymentEventType.objects.get(code=type_code)
75
+        event_type = order_models.PaymentEventType._default_manager.get(code=type_code)
76 76
         for line in lines.values():
77
-            order_models.PaymentEvent.objects.create(order=order, line=line, 
77
+            order_models.PaymentEvent._default_manager.create(order=order, line=line, 
78 78
                                                      quantity=line.quantity, event_type=event_type)
79 79
             
80 80
     def do_add_note(self, order):
@@ -83,7 +83,7 @@ class OrderView(ModelView):
83 83
             message = self.request.POST['message'].strip()
84 84
             if message:
85 85
                 messages.info(self.request, "Message added")
86
-                order_models.OrderNote.objects.create(order=order, message=self.request.POST['message'],
86
+                order_models.OrderNote._default_manager.create(order=order, message=self.request.POST['message'],
87 87
                                                          user=self.request.user)
88 88
             else:
89 89
                 messages.info(self.request, "Please enter a message")

+ 6
- 1
oscar/payment/abstract_models.py Ver fichero

@@ -30,7 +30,12 @@ class AbstractSource(models.Model):
30 30
     
31 31
     
32 32
 class AbstractSourceType(models.Model):
33
-    u"""A type of payment source (eg Bankcard, Business account, Gift card)"""
33
+    u"""
34
+    A type of payment source.
35
+    
36
+    This could be an external partner like PayPal or DataCash,
37
+    or an internal source such as a managed account.i
38
+    """
34 39
     name = models.CharField(max_length=128)
35 40
     code = models.SlugField(max_length=128, help_text="""This is used within
36 41
         forms to identify this source type""")

+ 1
- 1
oscar/payment/forms.py Ver fichero

@@ -175,7 +175,7 @@ class BillingAddressForm(forms.ModelForm):
175 175
         self.set_country_queryset() 
176 176
         
177 177
     def set_country_queryset(self):    
178
-        self.fields['country'].queryset = address_models.Country.objects.all()
178
+        self.fields['country'].queryset = address_models.Country._default_manager.all()
179 179
     
180 180
     class Meta:
181 181
         model = order_models.BillingAddress

+ 4
- 2
oscar/product/abstract_models.py Ver fichero

@@ -176,8 +176,8 @@ class AbstractItem(models.Model):
176 176
 
177 177
 class AbstractAttributeType(models.Model):
178 178
     u"""Defines an attribute. (Eg. size)"""
179
-    code = models.CharField(_('code'), max_length=128)
180 179
     name = models.CharField(_('name'), max_length=128)
180
+    code = models.SlugField(_('code'), max_length=128)
181 181
     has_choices = models.BooleanField(default=False)
182 182
 
183 183
     class Meta:
@@ -207,7 +207,9 @@ class AbstractAttributeValueOption(models.Model):
207 207
 
208 208
 class AbstractItemAttributeValue(models.Model):
209 209
     u"""
210
-    A specific attribute value for an item.
210
+    The "through" model for the m2m relationship between product.Item
211
+    and product.AttributeType.  This specifies the value of the attribute
212
+    for a particular product.
211 213
     
212 214
     Eg: size = L
213 215
     """

+ 4
- 1
oscar/product/admin.py Ver fichero

@@ -14,9 +14,12 @@ class ItemAdmin(admin.ModelAdmin):
14 14
     list_display = ('get_title', 'upc', 'get_item_class', 'is_top_level', 'is_group', 'is_variant', 'attribute_summary', 'date_created')
15 15
     prepopulated_fields = {"slug": ("title",)}
16 16
     inlines = [AttributeInline]
17
+    
18
+class AttributeTypeAdmin(admin.ModelAdmin):
19
+    prepopulated_fields = {"code": ("name",)}
17 20
 
18 21
 admin.site.register(product_models.ItemClass, ItemClassAdmin)
19 22
 admin.site.register(product_models.Item, ItemAdmin)
20
-admin.site.register(product_models.AttributeType)
23
+admin.site.register(product_models.AttributeType, AttributeTypeAdmin)
21 24
 admin.site.register(product_models.ItemAttributeValue)
22 25
 admin.site.register(product_models.Option)

+ 2
- 2
oscar/shipping/repository.py Ver fichero

@@ -19,7 +19,7 @@ class Repository(object):
19 19
         this behaviour can easily be overridden by subclassing this class
20 20
         and overriding this method.
21 21
         """ 
22
-        methods = shipping_models.OrderAndItemLevelChargeMethod.objects.all()
22
+        methods = shipping_models.OrderAndItemLevelChargeMethod._default_manager.all()
23 23
         if not methods.count():
24 24
             return [FreeShipping()]
25 25
         
@@ -33,4 +33,4 @@ class Repository(object):
33 33
         """
34 34
         if code == FreeShipping.code:
35 35
             return FreeShipping()
36
-        return shipping_models.OrderAndItemLevelChargeMethod.objects.get(code=code)          
36
+        return shipping_models.OrderAndItemLevelChargeMethod._default_manager.get(code=code)          

+ 3
- 0
oscar/stock/abstract_models.py Ver fichero

@@ -37,6 +37,9 @@ class AbstractStockRecord(models.Model):
37 37
     # This is the base price for calculations
38 38
     price_excl_tax = models.DecimalField(decimal_places=2, max_digits=12)
39 39
     
40
+    # Cost price is optional as not all partner supply it
41
+    cost_price = models.DecimalField(decimal_places=2, max_digits=12, blank=True, null=True)
42
+    
40 43
     # Stock level information
41 44
     num_in_stock = models.IntegerField(default=0)
42 45
     num_allocated = models.IntegerField(default=0)

+ 4
- 4
oscar/utils.py Ver fichero

@@ -6,9 +6,9 @@ def create_product(price=None):
6 6
     Helper method for creating products that are used in 
7 7
     tests.
8 8
     """
9
-    ic,_ = ItemClass.objects.get_or_create(name="Dummy item class")
10
-    item = Item.objects.create(title="Dummy product", item_class=ic)
9
+    ic,_ = ItemClass._default_manager.get_or_create(name="Dummy item class")
10
+    item = Item._default_manager.create(title="Dummy product", item_class=ic)
11 11
     if price:
12
-        partner,_ = Partner.objects.get_or_create(name="Dummy partner")
13
-        sr = StockRecord.objects.create(product=item, partner=partner, price_excl_tax=price)
12
+        partner,_ = Partner._default_manager.get_or_create(name="Dummy partner")
13
+        sr = StockRecord._default_manager.create(product=item, partner=partner, price_excl_tax=price)
14 14
     return item

Loading…
Cancelar
Guardar