Browse Source

Updated product management to allow shipping events to be set

master
David Winterbottom 14 years ago
parent
commit
0bf953c56e

+ 4
- 54
oscar/basket/views.py View File

@@ -4,6 +4,7 @@ from django.shortcuts import render_to_response, get_object_or_404, render
4 4
 from django.core.urlresolvers import reverse
5 5
 from django.contrib import messages
6 6
 
7
+from oscar.views import ModelView
7 8
 from oscar.services import import_module
8 9
 
9 10
 basket_models = import_module('basket.models', ['Basket', 'Line', 'InvalidBasketLineError'])
@@ -11,57 +12,6 @@ basket_forms = import_module('basket.forms', ['FormFactory'])
11 12
 basket_factory = import_module('basket.factory', ['get_or_create_open_basket', 'get_open_basket', 
12 13
                                                   'get_or_create_saved_basket', 'get_saved_basket'])
13 14
 product_models = import_module('product.models', ['Item'])
14
-
15
-
16
-class ModelView(object):
17
-    """
18
-    A generic view for models which can recieve GET and POST requests
19
-    
20
-    The __init__ method of subclasses should set the default response 
21
-    variable.
22
-    """
23
-    template_file = None
24
-    response = None
25
-        
26
-    def __call__(self, request, *args, **kwargs):
27
-        
28
-        self.request = request
29
-        self.args = args
30
-        self.kwargs = kwargs
31
-        
32
-        try:
33
-            method_name = "handle_%s" % request.method.upper()
34
-            getattr(self, method_name)()
35
-        except AttributeError:
36
-            # This class doesn't implement this HTTP method
37
-            messages.error(request, "Invalid action")
38
-        return self.response
39
-        
40
-    def handle_GET(self):
41
-        """
42
-        Default implementation of model view is to do nothing.
43
-        """ 
44
-        pass
45
-    
46
-    def handle_POST(self):
47
-        """
48
-        Handle a POST request to this resource.
49
-        
50
-        This will forward on request to a method of form "do_%s" where the
51
-        second part needs to be specified as an "action" name within the
52
-        request.
53
-        
54
-        If you don't want to handle POSTs this way, just override this method
55
-        """
56
-        if 'action' in self.request.POST:
57
-            model = self.get_model()
58
-            getattr(self, "do_%s" % self.request.POST['action'].lower())(model)
59
-            
60
-    def get_model(self):
61
-        """
62
-        Responsible for loading the model that is being acted on
63
-        """
64
-        pass
65 15
     
66 16
         
67 17
 class BasketView(ModelView):
@@ -73,16 +23,16 @@ class BasketView(ModelView):
73 23
     def get_model(self):
74 24
         return basket_factory.get_or_create_open_basket(self.request, self.response)
75 25
     
76
-    def handle_GET(self):
26
+    def handle_GET(self, basket):
77 27
         basket = basket_factory.get_open_basket(self.request)
78 28
         saved_basket = basket_factory.get_saved_basket(self.request)
79 29
         if not basket:
80 30
             basket = basket_models.Basket()
81 31
         self.response = render(self.request, self.template_file, locals())
82 32
         
83
-    def handle_POST(self):
33
+    def handle_POST(self, basket):
84 34
         try:
85
-            super(BasketView, self).handle_POST()
35
+            super(BasketView, self).handle_POST(basket)
86 36
         except basket_models.Basket.DoesNotExist:
87 37
             messages.error(self.request, "Unable to find your basket")
88 38
         except basket_models.InvalidBasketLineError, e:

+ 14
- 9
oscar/checkout/views.py View File

@@ -211,15 +211,20 @@ class SubmitView(object):
211 211
         order.save()
212 212
         return order
213 213
     
214
-    def _create_line_model(self, order, batch, line):
215
-        batch_line = order_models.BatchLine.objects.create(batch=batch, 
216
-                                                           product=line.product, 
217
-                                                           quantity=line.quantity, 
218
-                                                           line_price_excl_tax=line.line_price_excl_tax, 
219
-                                                           line_price_incl_tax=line.line_price_incl_tax)
220
-        order_models.BatchLinePrice.objects.create(line=batch_line, quantity=line.quantity, 
221
-                                                   price_incl_tax=line.unit_price_incl_tax,
222
-                                                   price_excl_tax=line.unit_price_excl_tax)
214
+    def _create_line_model(self, order, batch, basket_line):
215
+        batch_line = order_models.BatchLine.objects.create(order=order,
216
+                                                           batch=batch, 
217
+                                                           product=basket_line.product, 
218
+                                                           quantity=basket_line.quantity, 
219
+                                                           line_price_excl_tax=basket_line.line_price_excl_tax, 
220
+                                                           line_price_incl_tax=basket_line.line_price_incl_tax)
221
+        self._create_line_price_model(batch_line, basket_line)
222
+        
223
+    def _create_line_price_model(self, batch_line, basket_line):
224
+        order_models.BatchLinePrice.objects.create(line=batch_line, 
225
+                                                   quantity=batch_line.quantity, 
226
+                                                   price_incl_tax=basket_line.unit_price_incl_tax,
227
+                                                   price_excl_tax=basket_line.unit_price_excl_tax)
223 228
     
224 229
     def _get_or_create_batch_for_line(self, order, line):
225 230
          partner = self._get_partner_for_product(line.product)

+ 19
- 3
oscar/order/abstract_models.py View File

@@ -1,7 +1,11 @@
1 1
 from django.db import models
2 2
 from django.contrib.auth.models import User
3
+from django.template.defaultfilters import slugify
3 4
 from django.utils.translation import ugettext_lazy as _
4 5
 
6
+
7
+
8
+
5 9
 class AbstractOrder(models.Model):
6 10
     """
7 11
     An order
@@ -77,6 +81,7 @@ class AbstractBatchLine(models.Model):
77 81
     Not using a line model as it's difficult to capture and payment 
78 82
     information when it splits across a line.
79 83
     """
84
+    order = models.ForeignKey('order.Order', related_name='lines')
80 85
     batch = models.ForeignKey('order.Batch', related_name='lines')
81 86
     product = models.ForeignKey('product.Item')
82 87
     quantity = models.PositiveIntegerField(default=1)
@@ -151,7 +156,7 @@ class AbstractPaymentEvent(models.Model):
151 156
 
152 157
 class AbstractPaymentEventType(models.Model):
153 158
     """ 
154
-    Payment events are things like 'OrderPlaced', 'Acknowledged', 'Dispatched', 'Refunded'
159
+    Payment events are things like 'Paid', 'Failed', 'Refunded'
155 160
     """
156 161
     # Code is used in forms
157 162
     code = models.CharField(max_length=128)
@@ -160,9 +165,15 @@ class AbstractPaymentEventType(models.Model):
160 165
     # The normal order in which these shipping events take place
161 166
     order = models.PositiveIntegerField(default=0)
162 167
     
168
+    def save(self, *args, **kwargs):
169
+        if not self.code:
170
+            self.code = slugify(self.name)
171
+        super(AbstractPaymentEventType, self).save(*args, **kwargs)
172
+    
163 173
     class Meta:
164 174
         abstract = True
165 175
         verbose_name_plural = _("Payment event types")
176
+        ordering = ('order',)
166 177
         
167 178
     def __unicode__(self):
168 179
         return self.name
@@ -185,8 +196,8 @@ class AbstractShippingEvent(models.Model):
185 196
         verbose_name_plural = _("Shipping events")
186 197
         
187 198
     def __unicode__(self):
188
-        return u"Order #%d, batch #%d, line %s: %d items %s" % (
189
-            self.line.batch.order.number, self.line.batch.id, self.line.line_id, self.quantity, self.event_type)
199
+        return u"Order #%d, line %s: %d items set to '%s'" % (
200
+            self.line.batch.order.number, self.line.batch.id, self.line.id, self.quantity, self.event_type)
190 201
 
191 202
 
192 203
 class AbstractShippingEventType(models.Model):
@@ -200,6 +211,11 @@ class AbstractShippingEventType(models.Model):
200 211
     # The normal order in which these shipping events take place
201 212
     order = models.PositiveIntegerField(default=0)
202 213
     
214
+    def save(self, *args, **kwargs):
215
+        if not self.code:
216
+            self.code = slugify(self.name)
217
+        super(AbstractShippingEventType, self).save(*args, **kwargs)
218
+    
203 219
     class Meta:
204 220
         abstract = True
205 221
         verbose_name_plural = _("Shipping event types")

+ 8
- 2
oscar/order/admin.py View File

@@ -7,6 +7,12 @@ class BatchAdmin(admin.ModelAdmin):
7 7
 class BatchLineAdmin(admin.ModelAdmin):
8 8
     list_display = ('batch', 'product', 'quantity')
9 9
 
10
+class ShippingEventTypeAdmin(admin.ModelAdmin):
11
+    exclude = ('code',)
12
+    
13
+class PaymentEventTypeAdmin(admin.ModelAdmin):
14
+    exclude = ('code',)
15
+
10 16
 admin.site.register(Order)
11 17
 admin.site.register(BillingAddress)
12 18
 admin.site.register(Batch, BatchAdmin)
@@ -14,8 +20,8 @@ admin.site.register(ShippingAddress)
14 20
 admin.site.register(BatchLine, BatchLineAdmin)
15 21
 admin.site.register(BatchLinePrice)
16 22
 admin.site.register(ShippingEvent)
17
-admin.site.register(ShippingEventType)
23
+admin.site.register(ShippingEventType, ShippingEventTypeAdmin)
18 24
 admin.site.register(PaymentEvent)
19
-admin.site.register(PaymentEventType)
25
+admin.site.register(PaymentEventType, PaymentEventTypeAdmin)
20 26
 admin.site.register(BatchLineAttribute)
21 27
 

+ 1
- 1
oscar/order/fixtures/initial_data.json View File

@@ -1 +1 @@
1
-[{"pk": 1, "model": "order.shippingeventtype", "fields": {"code": "order_placed", "name": "Order placed", "order": 0}}, {"pk": 2, "model": "order.shippingeventtype", "fields": {"code": "dispatched", "name": "Dispatched", "order": 1}}, {"pk": 4, "model": "order.shippingeventtype", "fields": {"code": "returned", "name": "Returned", "order": 4}}, {"pk": 3, "model": "order.shippingeventtype", "fields": {"code": "cancelled", "name": "Cancelled", "order": 10}}]
1
+[{"pk": 1, "model": "order.shippingeventtype", "fields": {"code": "order_placed", "name": "Order placed", "order": 0}}, {"pk": 2, "model": "order.shippingeventtype", "fields": {"code": "dispatched", "name": "Dispatched", "order": 1}}, {"pk": 4, "model": "order.shippingeventtype", "fields": {"code": "returned", "name": "Returned", "order": 4}}, {"pk": 3, "model": "order.shippingeventtype", "fields": {"code": "cancelled", "name": "Cancelled", "order": 10}}, {"pk": 1, "model": "order.paymenteventtype", "fields": {"code": "paid-for", "name": "Paid for", "order": 0}}, {"pk": 2, "model": "order.paymenteventtype", "fields": {"code": "payment-failed", "name": "Payment failed", "order": 1}}, {"pk": 3, "model": "order.paymenteventtype", "fields": {"code": "refunded", "name": "Refunded", "order": 2}}]

+ 30
- 3
oscar/order_management/templates/order_management/order.html View File

@@ -7,8 +7,12 @@
7 7
 
8 8
 {% block content %}
9 9
 
10
+<h3>Order contents</h3>
11
+<form action="{% url oscar-order-management-order order.number %}" method="post">
12
+{% csrf_token %}
10 13
 <table>
11 14
     <tr>
15
+        <th></th>
12 16
         <th>Product</th>
13 17
         <th>Availability</th>
14 18
         <th>Quantity</th>
@@ -18,6 +22,7 @@
18 22
     {% for batch in order.batches.all %}
19 23
         {% for line in batch.lines.all %}
20 24
         <tr>
25
+            <th><input type="checkbox" name="order_line" value="{{ line.id }}"/></th>
21 26
             <td><a href="{{ line.product.get_absolute_url }}">{{ line.description }}</a></td>
22 27
             <td>{{ line.product.stockrecord.availability }}</td>
23 28
             <td>{{ line.quantity }}</td>
@@ -27,20 +32,42 @@
27 32
         {% endfor %}
28 33
     {% endfor %}
29 34
     <tr>
30
-        <td colspan="3">Basket total</td>
35
+        <th colspan="3">Basket total</th>
31 36
         <td>{{ order.basket_total_excl_tax|floatformat:2 }}</td>
32 37
         <td>{{ order.basket_total_incl_tax|floatformat:2 }}</td>
33 38
     </tr>
34 39
     <tr>
35
-        <td colspan="3">Shipping charge</td>
40
+        <th colspan="3">Shipping charge</th>
36 41
         <td>{{ order.shipping_excl_tax|floatformat:2 }}</td>
37 42
         <td>{{ order.shipping_incl_tax|floatformat:2 }}</td>
38 43
     </tr>
39 44
     <tr>
40
-        <td colspan="4">Order total</td>
45
+        <th colspan="4">Order total</th>
41 46
         <td>{{ order.total_incl_tax|floatformat:2 }}</td>
42 47
     </tr>
43 48
 </table>
44 49
 
50
+<p>Update SHIPPING STATUS of selected lines to:
51
+   <select name="shipping_event">
52
+       <option value="">[Choose option]</option>
53
+       {% for type in shipping_options %}
54
+       <option value="{{ type.code }}">{{type.name}}</option>
55
+       {% endfor %}
56
+   </select>  
57
+   <input type="submit" value="Save" />
58
+</p>
59
+<p>Update PAYMENT STATUS of selected lines to:
60
+    <select name="payment_event">
61
+        <option value="">[Choose option]</option>
62
+        {% for type in payment_options %}
63
+           <option value="{{ type.code }}">{{type.name}}</option>
64
+        {% endfor %}
65
+    </select>  
66
+    <input type="submit" value="Save" />
67
+</p>
68
+</form>
69
+
70
+
71
+
45 72
 {% endblock content %}
46 73
 

+ 3
- 2
oscar/order_management/urls.py View File

@@ -1,9 +1,10 @@
1 1
 from django.conf.urls.defaults import *
2 2
 
3
-from oscar.order_management.views import OrderListView, OrderDetailView
3
+from oscar.views import class_based_view
4
+from oscar.order_management.views import OrderListView, OrderView
4 5
 
5 6
 urlpatterns = patterns('oscar.order_management.views',
6 7
     url(r'^$', OrderListView.as_view(), name='oscar-order-management-list'),
7
-    url(r'^order/(?P<order_number>[\w-]*)$', OrderDetailView.as_view(), name='oscar-order-management-order'),
8
+    url(r'^order/(?P<order_number>[\w-]*)/$', class_based_view(OrderView), name='oscar-order-management-order'),
8 9
 )
9 10
 

+ 30
- 8
oscar/order_management/views.py View File

@@ -5,8 +5,9 @@ from django.core.urlresolvers import reverse
5 5
 from django.core.paginator import Paginator, InvalidPage, EmptyPage
6 6
 from django.views.generic import ListView, DetailView
7 7
 
8
+from oscar.views import ModelView
8 9
 from oscar.services import import_module
9
-order_models = import_module('order.models', ['Order'])
10
+order_models = import_module('order.models', ['Order', 'BatchLine', 'ShippingEvent', 'ShippingEventType', 'PaymentEventType'])
10 11
 
11 12
 
12 13
 class OrderListView(ListView):
@@ -19,12 +20,33 @@ class OrderListView(ListView):
19 20
         return order_models.Order.objects.all()
20 21
     
21 22
     
22
-class OrderDetailView(DetailView):
23
-    """
24
-    View a single order.
25
-    """
26
-    context_object_name = "order"
27
-    template_name = "order_management/order.html"
23
+class OrderView(ModelView):
24
+    template_file = "order_management/order.html"
28 25
     
29
-    def get_object(self):
26
+    def get_model(self):
30 27
         return get_object_or_404(order_models.Order, number=self.kwargs['order_number'])
28
+    
29
+    def handle_GET(self, order):
30
+        shipping_options = order_models.ShippingEventType.objects.all()
31
+        payment_options = order_models.PaymentEventType.objects.all()
32
+        
33
+        self.response = render(self.request, self.template_file, locals())
34
+        
35
+    def handle_POST(self, order):
36
+        
37
+        self.response = HttpResponseRedirect(reverse('oscar-order-management-order', kwargs={'order_number': order.number}))
38
+        
39
+        line_ids = self.request.POST.getlist('order_line')
40
+        
41
+        # Need to determine what kind of event update this is
42
+        if self.request.POST['shipping_event']:
43
+            # Load event type obj
44
+            event_type = order_models.ShippingEventType.objects.get(code=self.request.POST['shipping_event'])
45
+            
46
+            # Need to load lines in a way that checks they are from the order 
47
+            lines = order_models.BatchLine.objects.in_bulk(line_ids)
48
+            for line in lines.values():
49
+                order_models.ShippingEvent.objects.create(line=line, quantity=line.quantity, event_type=event_type)
50
+        
51
+        
52
+    

+ 50
- 0
oscar/views.py View File

@@ -3,6 +3,8 @@ from django.http import HttpResponse, Http404, HttpResponseRedirect
3 3
 from django.template import Context, loader, RequestContext
4 4
 from django.shortcuts import render_to_response, get_object_or_404
5 5
 from django.core.urlresolvers import reverse
6
+from django.contrib import messages
7
+
6 8
 
7 9
 def class_based_view(class_obj):
8 10
     """
@@ -16,6 +18,54 @@ def class_based_view(class_obj):
16 18
     return _instantiate_view_class
17 19
 
18 20
 
21
+class ModelView(object):
22
+    """
23
+    A generic view for models which can recieve GET and POST requests
24
+    
25
+    The __init__ method of subclasses should set the default response 
26
+    variable.
27
+    """
28
+    template_file = None
29
+    response = None
30
+        
31
+    def __call__(self, request, *args, **kwargs):
32
+        
33
+        self.request = request
34
+        self.args = args
35
+        self.kwargs = kwargs
36
+        
37
+        method_name = "handle_%s" % request.method.upper()
38
+        model = self.get_model()
39
+        getattr(self, method_name)(model)
40
+        
41
+        return self.response
42
+        
43
+    def handle_GET(self, model):
44
+        """
45
+        Default implementation of model view is to do nothing.
46
+        """ 
47
+        pass
48
+    
49
+    def handle_POST(self, model):
50
+        """
51
+        Handle a POST request to this resource.
52
+        
53
+        This will forward on request to a method of form "do_%s" where the
54
+        second part needs to be specified as an "action" name within the
55
+        request.
56
+        
57
+        If you don't want to handle POSTs this way, just override this method
58
+        """
59
+        if 'action' in self.request.POST:
60
+            getattr(self, "do_%s" % self.request.POST['action'].lower())(model)
61
+            
62
+    def get_model(self):
63
+        """
64
+        Responsible for loading the model that is being acted on
65
+        """
66
+        return None
67
+
68
+
19 69
 def home(request):
20 70
     """ 
21 71
     Oscar home page

Loading…
Cancel
Save