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
 from django.core.urlresolvers import reverse
4
 from django.core.urlresolvers import reverse
5
 from django.contrib import messages
5
 from django.contrib import messages
6
 
6
 
7
+from oscar.views import ModelView
7
 from oscar.services import import_module
8
 from oscar.services import import_module
8
 
9
 
9
 basket_models = import_module('basket.models', ['Basket', 'Line', 'InvalidBasketLineError'])
10
 basket_models = import_module('basket.models', ['Basket', 'Line', 'InvalidBasketLineError'])
11
 basket_factory = import_module('basket.factory', ['get_or_create_open_basket', 'get_open_basket', 
12
 basket_factory = import_module('basket.factory', ['get_or_create_open_basket', 'get_open_basket', 
12
                                                   'get_or_create_saved_basket', 'get_saved_basket'])
13
                                                   'get_or_create_saved_basket', 'get_saved_basket'])
13
 product_models = import_module('product.models', ['Item'])
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
 class BasketView(ModelView):
17
 class BasketView(ModelView):
73
     def get_model(self):
23
     def get_model(self):
74
         return basket_factory.get_or_create_open_basket(self.request, self.response)
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
         basket = basket_factory.get_open_basket(self.request)
27
         basket = basket_factory.get_open_basket(self.request)
78
         saved_basket = basket_factory.get_saved_basket(self.request)
28
         saved_basket = basket_factory.get_saved_basket(self.request)
79
         if not basket:
29
         if not basket:
80
             basket = basket_models.Basket()
30
             basket = basket_models.Basket()
81
         self.response = render(self.request, self.template_file, locals())
31
         self.response = render(self.request, self.template_file, locals())
82
         
32
         
83
-    def handle_POST(self):
33
+    def handle_POST(self, basket):
84
         try:
34
         try:
85
-            super(BasketView, self).handle_POST()
35
+            super(BasketView, self).handle_POST(basket)
86
         except basket_models.Basket.DoesNotExist:
36
         except basket_models.Basket.DoesNotExist:
87
             messages.error(self.request, "Unable to find your basket")
37
             messages.error(self.request, "Unable to find your basket")
88
         except basket_models.InvalidBasketLineError, e:
38
         except basket_models.InvalidBasketLineError, e:

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

211
         order.save()
211
         order.save()
212
         return order
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
     def _get_or_create_batch_for_line(self, order, line):
229
     def _get_or_create_batch_for_line(self, order, line):
225
          partner = self._get_partner_for_product(line.product)
230
          partner = self._get_partner_for_product(line.product)

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

1
 from django.db import models
1
 from django.db import models
2
 from django.contrib.auth.models import User
2
 from django.contrib.auth.models import User
3
+from django.template.defaultfilters import slugify
3
 from django.utils.translation import ugettext_lazy as _
4
 from django.utils.translation import ugettext_lazy as _
4
 
5
 
6
+
7
+
8
+
5
 class AbstractOrder(models.Model):
9
 class AbstractOrder(models.Model):
6
     """
10
     """
7
     An order
11
     An order
77
     Not using a line model as it's difficult to capture and payment 
81
     Not using a line model as it's difficult to capture and payment 
78
     information when it splits across a line.
82
     information when it splits across a line.
79
     """
83
     """
84
+    order = models.ForeignKey('order.Order', related_name='lines')
80
     batch = models.ForeignKey('order.Batch', related_name='lines')
85
     batch = models.ForeignKey('order.Batch', related_name='lines')
81
     product = models.ForeignKey('product.Item')
86
     product = models.ForeignKey('product.Item')
82
     quantity = models.PositiveIntegerField(default=1)
87
     quantity = models.PositiveIntegerField(default=1)
151
 
156
 
152
 class AbstractPaymentEventType(models.Model):
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
     # Code is used in forms
161
     # Code is used in forms
157
     code = models.CharField(max_length=128)
162
     code = models.CharField(max_length=128)
160
     # The normal order in which these shipping events take place
165
     # The normal order in which these shipping events take place
161
     order = models.PositiveIntegerField(default=0)
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
     class Meta:
173
     class Meta:
164
         abstract = True
174
         abstract = True
165
         verbose_name_plural = _("Payment event types")
175
         verbose_name_plural = _("Payment event types")
176
+        ordering = ('order',)
166
         
177
         
167
     def __unicode__(self):
178
     def __unicode__(self):
168
         return self.name
179
         return self.name
185
         verbose_name_plural = _("Shipping events")
196
         verbose_name_plural = _("Shipping events")
186
         
197
         
187
     def __unicode__(self):
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
 class AbstractShippingEventType(models.Model):
203
 class AbstractShippingEventType(models.Model):
200
     # The normal order in which these shipping events take place
211
     # The normal order in which these shipping events take place
201
     order = models.PositiveIntegerField(default=0)
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
     class Meta:
219
     class Meta:
204
         abstract = True
220
         abstract = True
205
         verbose_name_plural = _("Shipping event types")
221
         verbose_name_plural = _("Shipping event types")

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

7
 class BatchLineAdmin(admin.ModelAdmin):
7
 class BatchLineAdmin(admin.ModelAdmin):
8
     list_display = ('batch', 'product', 'quantity')
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
 admin.site.register(Order)
16
 admin.site.register(Order)
11
 admin.site.register(BillingAddress)
17
 admin.site.register(BillingAddress)
12
 admin.site.register(Batch, BatchAdmin)
18
 admin.site.register(Batch, BatchAdmin)
14
 admin.site.register(BatchLine, BatchLineAdmin)
20
 admin.site.register(BatchLine, BatchLineAdmin)
15
 admin.site.register(BatchLinePrice)
21
 admin.site.register(BatchLinePrice)
16
 admin.site.register(ShippingEvent)
22
 admin.site.register(ShippingEvent)
17
-admin.site.register(ShippingEventType)
23
+admin.site.register(ShippingEventType, ShippingEventTypeAdmin)
18
 admin.site.register(PaymentEvent)
24
 admin.site.register(PaymentEvent)
19
-admin.site.register(PaymentEventType)
25
+admin.site.register(PaymentEventType, PaymentEventTypeAdmin)
20
 admin.site.register(BatchLineAttribute)
26
 admin.site.register(BatchLineAttribute)
21
 
27
 

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

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
 
7
 
8
 {% block content %}
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
 <table>
13
 <table>
11
     <tr>
14
     <tr>
15
+        <th></th>
12
         <th>Product</th>
16
         <th>Product</th>
13
         <th>Availability</th>
17
         <th>Availability</th>
14
         <th>Quantity</th>
18
         <th>Quantity</th>
18
     {% for batch in order.batches.all %}
22
     {% for batch in order.batches.all %}
19
         {% for line in batch.lines.all %}
23
         {% for line in batch.lines.all %}
20
         <tr>
24
         <tr>
25
+            <th><input type="checkbox" name="order_line" value="{{ line.id }}"/></th>
21
             <td><a href="{{ line.product.get_absolute_url }}">{{ line.description }}</a></td>
26
             <td><a href="{{ line.product.get_absolute_url }}">{{ line.description }}</a></td>
22
             <td>{{ line.product.stockrecord.availability }}</td>
27
             <td>{{ line.product.stockrecord.availability }}</td>
23
             <td>{{ line.quantity }}</td>
28
             <td>{{ line.quantity }}</td>
27
         {% endfor %}
32
         {% endfor %}
28
     {% endfor %}
33
     {% endfor %}
29
     <tr>
34
     <tr>
30
-        <td colspan="3">Basket total</td>
35
+        <th colspan="3">Basket total</th>
31
         <td>{{ order.basket_total_excl_tax|floatformat:2 }}</td>
36
         <td>{{ order.basket_total_excl_tax|floatformat:2 }}</td>
32
         <td>{{ order.basket_total_incl_tax|floatformat:2 }}</td>
37
         <td>{{ order.basket_total_incl_tax|floatformat:2 }}</td>
33
     </tr>
38
     </tr>
34
     <tr>
39
     <tr>
35
-        <td colspan="3">Shipping charge</td>
40
+        <th colspan="3">Shipping charge</th>
36
         <td>{{ order.shipping_excl_tax|floatformat:2 }}</td>
41
         <td>{{ order.shipping_excl_tax|floatformat:2 }}</td>
37
         <td>{{ order.shipping_incl_tax|floatformat:2 }}</td>
42
         <td>{{ order.shipping_incl_tax|floatformat:2 }}</td>
38
     </tr>
43
     </tr>
39
     <tr>
44
     <tr>
40
-        <td colspan="4">Order total</td>
45
+        <th colspan="4">Order total</th>
41
         <td>{{ order.total_incl_tax|floatformat:2 }}</td>
46
         <td>{{ order.total_incl_tax|floatformat:2 }}</td>
42
     </tr>
47
     </tr>
43
 </table>
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
 {% endblock content %}
72
 {% endblock content %}
46
 
73
 

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

1
 from django.conf.urls.defaults import *
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
 urlpatterns = patterns('oscar.order_management.views',
6
 urlpatterns = patterns('oscar.order_management.views',
6
     url(r'^$', OrderListView.as_view(), name='oscar-order-management-list'),
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
 from django.core.paginator import Paginator, InvalidPage, EmptyPage
5
 from django.core.paginator import Paginator, InvalidPage, EmptyPage
6
 from django.views.generic import ListView, DetailView
6
 from django.views.generic import ListView, DetailView
7
 
7
 
8
+from oscar.views import ModelView
8
 from oscar.services import import_module
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
 class OrderListView(ListView):
13
 class OrderListView(ListView):
19
         return order_models.Order.objects.all()
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
         return get_object_or_404(order_models.Order, number=self.kwargs['order_number'])
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
 from django.template import Context, loader, RequestContext
3
 from django.template import Context, loader, RequestContext
4
 from django.shortcuts import render_to_response, get_object_or_404
4
 from django.shortcuts import render_to_response, get_object_or_404
5
 from django.core.urlresolvers import reverse
5
 from django.core.urlresolvers import reverse
6
+from django.contrib import messages
7
+
6
 
8
 
7
 def class_based_view(class_obj):
9
 def class_based_view(class_obj):
8
     """
10
     """
16
     return _instantiate_view_class
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
 def home(request):
69
 def home(request):
20
     """ 
70
     """ 
21
     Oscar home page
71
     Oscar home page

Loading…
Cancel
Save