Преглед изворни кода

Merge branch 'master' into offer-proxy-models

Conflicts:
	docs/source/index.rst
	oscar/order/utils.py
master
David Winterbottom пре 15 година
родитељ
комит
f97c2d43ed

+ 1
- 6
docs/source/contributing.rst Прегледај датотеку

10
 * Pull requests will be rejected if sufficient tests aren't provided
10
 * Pull requests will be rejected if sufficient tests aren't provided
11
 * Please updated the documentation when altering behaviour or introducing new features 
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
 Contents:
14
 Contents:
21
 
15
 
24
 
18
 
25
    contributing/installation
19
    contributing/installation
26
    contributing/testing
20
    contributing/testing
21
+   contributing/conventions

+ 22
- 0
docs/source/contributing/conventions.rst Прегледај датотеку

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 Прегледај датотеку

62
     ./manage.py syncdb
62
     ./manage.py syncdb
63
 
63
 
64
 However, in reality you will need to start extending the models to match your domain.  It's best to do
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 Прегледај датотеку

32
    web_services
32
    web_services
33
    recipes
33
    recipes
34
    contributing
34
    contributing
35
-   settings
35
+   reference
36
 
36
 
37
 Indices and tables
37
 Indices and tables
38
 ==================
38
 ==================

+ 10
- 0
docs/source/reference.rst Прегледај датотеку

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

+ 31
- 0
docs/source/reference/settings.rst Прегледај датотеку

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 Прегледај датотеку

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 Прегледај датотеку

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 Прегледај датотеку

13
         self.set_country_queryset() 
13
         self.set_country_queryset() 
14
         
14
         
15
     def set_country_queryset(self):    
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
     class Meta:
18
     class Meta:
19
         model = order_models.ShippingAddress
19
         model = order_models.ShippingAddress

+ 12
- 131
oscar/checkout/views.py Прегледај датотеку

18
 checkout_calculators = import_module('checkout.calculators', ['OrderTotalCalculator'])
18
 checkout_calculators = import_module('checkout.calculators', ['OrderTotalCalculator'])
19
 checkout_utils = import_module('checkout.utils', ['ProgressChecker', 'CheckoutSessionData'])
19
 checkout_utils = import_module('checkout.utils', ['ProgressChecker', 'CheckoutSessionData'])
20
 checkout_signals = import_module('checkout.signals', ['order_placed', 'pre_payment', 'post_payment'])
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
 order_models = import_module('order.models', ['Order', 'ShippingAddress'])
22
 order_models = import_module('order.models', ['Order', 'ShippingAddress'])
22
 order_utils = import_module('order.utils', ['OrderNumberGenerator', 'OrderCreator'])
23
 order_utils = import_module('order.utils', ['OrderNumberGenerator', 'OrderCreator'])
23
 address_models = import_module('address.models', ['UserAddress'])
24
 address_models = import_module('address.models', ['UserAddress'])
24
 shipping_repository = import_module('shipping.repository', ['Repository'])
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
 class IndexView(object):
28
 class IndexView(object):
148
     template_file = 'checkout/gateway.html'
29
     template_file = 'checkout/gateway.html'
153
         return render(request, self.template_file, locals())    
34
         return render(request, self.template_file, locals())    
154
 
35
 
155
 
36
 
156
-class ShippingAddressView(CheckoutView):
37
+class ShippingAddressView(checkout_views.CheckoutView):
157
     template_file = 'checkout/shipping_address.html'
38
     template_file = 'checkout/shipping_address.html'
158
     
39
     
159
     def handle_POST(self):
40
     def handle_POST(self):
160
         if self.request.user.is_authenticated and 'address_id' in self.request.POST:
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
             if 'action' in self.request.POST and self.request.POST['action'] == 'ship_to':
43
             if 'action' in self.request.POST and self.request.POST['action'] == 'ship_to':
163
                 # User has selected a previous address to ship to
44
                 # User has selected a previous address to ship to
164
                 self.co_data.ship_to_user_address(address)
45
                 self.co_data.ship_to_user_address(address)
188
     
69
     
189
         # Look up address book data
70
         # Look up address book data
190
         if self.request.user.is_authenticated():
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
         return render(self.request, self.template_file, self.context)
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
     u"""
78
     u"""
198
     Shipping methods are domain-specific and so need implementing in a 
79
     Shipping methods are domain-specific and so need implementing in a 
199
     subclass of this class.
80
     subclass of this class.
224
         return self.get_success_response()
105
         return self.get_success_response()
225
         
106
         
226
 
107
 
227
-class PaymentMethodView(CheckoutView):
108
+class PaymentMethodView(checkout_views.CheckoutView):
228
     u"""
109
     u"""
229
     View for a user to choose which payment method(s) they want to use.
110
     View for a user to choose which payment method(s) they want to use.
230
     
111
     
234
     pass
115
     pass
235
 
116
 
236
 
117
 
237
-class OrderPreviewView(CheckoutView):
118
+class OrderPreviewView(checkout_views.CheckoutView):
238
     u"""View a preview of the order before submitting."""
119
     u"""View a preview of the order before submitting."""
239
     
120
     
240
     template_file = 'checkout/preview.html'
121
     template_file = 'checkout/preview.html'
241
     
122
     
242
     def handle_GET(self):
123
     def handle_GET(self):
243
-        mark_step_as_complete(self.request)
124
+        checkout_views.mark_step_as_complete(self.request)
244
         return render(self.request, self.template_file, self.context)
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
     u"""
129
     u"""
249
     For taking the details of payment and creating the order
130
     For taking the details of payment and creating the order
250
     
131
     
359
             # Check that this address isn't already in the db as we don't want
240
             # Check that this address isn't already in the db as we don't want
360
             # to fill up the customer address book with duplicate addresses
241
             # to fill up the customer address book with duplicate addresses
361
             try:
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
             except ObjectDoesNotExist:
244
             except ObjectDoesNotExist:
364
                 user_addr.save()
245
                 user_addr.save()
365
     
246
     
366
     def _create_shipping_address_from_user_address(self, addr_id):
247
     def _create_shipping_address_from_user_address(self, addr_id):
367
         u"""Creates a shipping address from a user address"""
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
         # Increment the number of orders to help determine popularity of orders 
250
         # Increment the number of orders to help determine popularity of orders 
370
         address.num_orders += 1
251
         address.num_orders += 1
371
         address.save()
252
         address.save()
380
     
261
     
381
     def __call__(self, request):
262
     def __call__(self, request):
382
         try:
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
             # Remove order number from session to ensure that the thank-you page is only 
266
             # Remove order number from session to ensure that the thank-you page is only 
386
             # viewable once.
267
             # viewable once.

+ 3
- 3
oscar/customer/views.py Прегледај датотеку

14
 def profile(request):
14
 def profile(request):
15
     u"""Return a customers's profile"""
15
     u"""Return a customers's profile"""
16
     # Load last 5 orders as preview
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
     return render(request, 'customer/profile.html', locals())
18
     return render(request, 'customer/profile.html', locals())
19
     
19
     
20
         
20
         
26
 
26
 
27
     def get_queryset(self):
27
     def get_queryset(self):
28
         u"""Return a customer's orders"""
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
 class OrderDetailView(ModelView):
32
 class OrderDetailView(ModelView):
49
         
49
         
50
     def get_queryset(self):
50
     def get_queryset(self):
51
         u"""Return a customer's addresses"""
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
 class AddressView(ModelView):
55
 class AddressView(ModelView):

+ 18
- 7
oscar/order/abstract_models.py Прегледај датотеку

136
     information when it splits across a line.
136
     information when it splits across a line.
137
     """
137
     """
138
     order = models.ForeignKey('order.Order', related_name='lines')
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
     partner = models.ForeignKey('stock.Partner', related_name='order_lines')
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
     quantity = models.PositiveIntegerField(default=1)
147
     quantity = models.PositiveIntegerField(default=1)
148
+    
142
     # Price information (these fields are actually redundant as the information
149
     # Price information (these fields are actually redundant as the information
143
     # can be calculated from the LinePrice models
150
     # can be calculated from the LinePrice models
144
     line_price_incl_tax = models.DecimalField(decimal_places=2, max_digits=12)
151
     line_price_incl_tax = models.DecimalField(decimal_places=2, max_digits=12)
145
     line_price_excl_tax = models.DecimalField(decimal_places=2, max_digits=12)
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
     # Partner information
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
         help_text=_("This is the item number that the partner uses within their system"))
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
     @property
163
     @property
153
     def description(self):
164
     def description(self):
324
 
335
 
325
     def _check_previous_events_are_complete(self):
336
     def _check_previous_events_are_complete(self):
326
         u"""Checks whether previous shipping events have passed"""
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
         self.quantity = int(self.quantity)
340
         self.quantity = int(self.quantity)
330
         for event_quantities in previous_events:
341
         for event_quantities in previous_events:
331
             if event_quantities.quantity < self.quantity:
342
             if event_quantities.quantity < self.quantity:
332
                 raise ValueError("Invalid quantity (%d) for event type (a previous event has not been fully passed)" % self.quantity)
343
                 raise ValueError("Invalid quantity (%d) for event type (a previous event has not been fully passed)" % self.quantity)
333
 
344
 
334
     def _check_new_quantity(self):
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
         previous_quantity = quantity_row['quantity__sum']
348
         previous_quantity = quantity_row['quantity__sum']
338
         if previous_quantity == None:
349
         if previous_quantity == None:
339
             previous_quantity = 0
350
             previous_quantity = 0

+ 2
- 2
oscar/order/fixtures/sample-order.json Прегледај датотеку

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

+ 12
- 9
oscar/order/utils.py Прегледај датотеку

47
         calc = self.order_total_calculator
47
         calc = self.order_total_calculator
48
         order_data = {'basket': basket,
48
         order_data = {'basket': basket,
49
                       'number': order_number,
49
                       'number': order_number,
50
-                      'site': Site.objects.get_current(),
50
+                      'site': Site._default_manager.get_current(),
51
                       'total_incl_tax': calc.order_total_incl_tax(basket, shipping_method),
51
                       'total_incl_tax': calc.order_total_incl_tax(basket, shipping_method),
52
                       'total_excl_tax': calc.order_total_excl_tax(basket, shipping_method),
52
                       'total_excl_tax': calc.order_total_excl_tax(basket, shipping_method),
53
                       'shipping_address': shipping_address,
53
                       'shipping_address': shipping_address,
68
     
68
     
69
     def _create_line_models(self, order, basket_line):
69
     def _create_line_models(self, order, basket_line):
70
         u"""Creates the batch line model."""
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
         self._create_line_price_models(order, order_line, basket_line)
80
         self._create_line_price_models(order, order_line, basket_line)
78
         self._create_line_attributes(order, order_line, basket_line)
81
         self._create_line_attributes(order, order_line, basket_line)
79
         
82
         
81
         u"""Creates the batch line price models"""
84
         u"""Creates the batch line price models"""
82
         breakdown = basket_line.get_price_breakdown()
85
         breakdown = basket_line.get_price_breakdown()
83
         for price_incl_tax, price_excl_tax, quantity in breakdown:
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
                                                   line=order_line, 
88
                                                   line=order_line, 
86
                                                   quantity=quantity, 
89
                                                   quantity=quantity, 
87
                                                   price_incl_tax=price_incl_tax,
90
                                                   price_incl_tax=price_incl_tax,
90
     def _create_line_attributes(self, order, order_line, basket_line):
93
     def _create_line_attributes(self, order, order_line, basket_line):
91
         u"""Creates the batch line attributes."""
94
         u"""Creates the batch line attributes."""
92
         for attr in basket_line.attributes.all():
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
                                                       value=attr.value)
97
                                                       value=attr.value)

+ 9
- 9
oscar/order_management/views.py Прегледај датотеку

20
     paginate_by = 20
20
     paginate_by = 20
21
 
21
 
22
     def get_queryset(self):
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
 class OrderView(ModelView):
26
 class OrderView(ModelView):
32
         return get_object_or_404(order_models.Order, number=self.kwargs['order_number'])
32
         return get_object_or_404(order_models.Order, number=self.kwargs['order_number'])
33
     
33
     
34
     def handle_GET(self, order):
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
         self.response = render(self.request, self.template_file, locals())
38
         self.response = render(self.request, self.template_file, locals())
39
         
39
         
60
     def create_shipping_event(self, order, lines):
60
     def create_shipping_event(self, order, lines):
61
         u"""Create a shipping event for an order"""
61
         u"""Create a shipping event for an order"""
62
         with transaction.commit_on_success():
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
             for line in lines:
65
             for line in lines:
66
                 try:
66
                 try:
67
                     event_quantity = int(self.request.POST['order_line_quantity_%d' % line.id])
67
                     event_quantity = int(self.request.POST['order_line_quantity_%d' % line.id])
68
                 except KeyError:
68
                 except KeyError:
69
                     event_quantity = line.quantity
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
                                                                   quantity=event_quantity)
71
                                                                   quantity=event_quantity)
72
             
72
             
73
     def create_payment_event(self, order, lines, type_code):
73
     def create_payment_event(self, order, lines, type_code):
74
         u"""Create a payment event for an order"""
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
         for line in lines.values():
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
                                                      quantity=line.quantity, event_type=event_type)
78
                                                      quantity=line.quantity, event_type=event_type)
79
             
79
             
80
     def do_add_note(self, order):
80
     def do_add_note(self, order):
83
             message = self.request.POST['message'].strip()
83
             message = self.request.POST['message'].strip()
84
             if message:
84
             if message:
85
                 messages.info(self.request, "Message added")
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
                                                          user=self.request.user)
87
                                                          user=self.request.user)
88
             else:
88
             else:
89
                 messages.info(self.request, "Please enter a message")
89
                 messages.info(self.request, "Please enter a message")

+ 6
- 1
oscar/payment/abstract_models.py Прегледај датотеку

30
     
30
     
31
     
31
     
32
 class AbstractSourceType(models.Model):
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
     name = models.CharField(max_length=128)
39
     name = models.CharField(max_length=128)
35
     code = models.SlugField(max_length=128, help_text="""This is used within
40
     code = models.SlugField(max_length=128, help_text="""This is used within
36
         forms to identify this source type""")
41
         forms to identify this source type""")

+ 1
- 1
oscar/payment/forms.py Прегледај датотеку

175
         self.set_country_queryset() 
175
         self.set_country_queryset() 
176
         
176
         
177
     def set_country_queryset(self):    
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
     class Meta:
180
     class Meta:
181
         model = order_models.BillingAddress
181
         model = order_models.BillingAddress

+ 4
- 2
oscar/product/abstract_models.py Прегледај датотеку

176
 
176
 
177
 class AbstractAttributeType(models.Model):
177
 class AbstractAttributeType(models.Model):
178
     u"""Defines an attribute. (Eg. size)"""
178
     u"""Defines an attribute. (Eg. size)"""
179
-    code = models.CharField(_('code'), max_length=128)
180
     name = models.CharField(_('name'), max_length=128)
179
     name = models.CharField(_('name'), max_length=128)
180
+    code = models.SlugField(_('code'), max_length=128)
181
     has_choices = models.BooleanField(default=False)
181
     has_choices = models.BooleanField(default=False)
182
 
182
 
183
     class Meta:
183
     class Meta:
207
 
207
 
208
 class AbstractItemAttributeValue(models.Model):
208
 class AbstractItemAttributeValue(models.Model):
209
     u"""
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
     Eg: size = L
214
     Eg: size = L
213
     """
215
     """

+ 4
- 1
oscar/product/admin.py Прегледај датотеку

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

+ 2
- 2
oscar/shipping/repository.py Прегледај датотеку

19
         this behaviour can easily be overridden by subclassing this class
19
         this behaviour can easily be overridden by subclassing this class
20
         and overriding this method.
20
         and overriding this method.
21
         """ 
21
         """ 
22
-        methods = shipping_models.OrderAndItemLevelChargeMethod.objects.all()
22
+        methods = shipping_models.OrderAndItemLevelChargeMethod._default_manager.all()
23
         if not methods.count():
23
         if not methods.count():
24
             return [FreeShipping()]
24
             return [FreeShipping()]
25
         
25
         
33
         """
33
         """
34
         if code == FreeShipping.code:
34
         if code == FreeShipping.code:
35
             return FreeShipping()
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 Прегледај датотеку

37
     # This is the base price for calculations
37
     # This is the base price for calculations
38
     price_excl_tax = models.DecimalField(decimal_places=2, max_digits=12)
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
     # Stock level information
43
     # Stock level information
41
     num_in_stock = models.IntegerField(default=0)
44
     num_in_stock = models.IntegerField(default=0)
42
     num_allocated = models.IntegerField(default=0)
45
     num_allocated = models.IntegerField(default=0)

+ 4
- 4
oscar/utils.py Прегледај датотеку

6
     Helper method for creating products that are used in 
6
     Helper method for creating products that are used in 
7
     tests.
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
     if price:
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
     return item
14
     return item

Loading…
Откажи
Сачувај