Просмотр исходного кода

First version of orders dashboard now working

master
David Winterbottom 14 лет назад
Родитель
Сommit
02f928f1dc

+ 2
- 0
oscar/apps/dashboard/app.py Просмотреть файл

@@ -10,10 +10,12 @@ class DashboardApplication(Application):
10 10
     name = 'dashboard'
11 11
     
12 12
     index_view = views.IndexView
13
+    orders_view = views.OrderListView
13 14
 
14 15
     def get_urls(self):
15 16
         urlpatterns = patterns('',
16 17
             url(r'^$', self.index_view.as_view(), name='index'),
18
+            url(r'^orders/$', self.orders_view.as_view(), name='orders'),
17 19
         )
18 20
         return self.post_process_urls(urlpatterns)
19 21
 

+ 22
- 0
oscar/apps/dashboard/forms.py Просмотреть файл

@@ -0,0 +1,22 @@
1
+from django import forms
2
+
3
+
4
+class OrderSearchForm(forms.Form):
5
+    order_number = forms.CharField(required=False, label="Order number")
6
+    name = forms.CharField(required=False, label="Customer name")
7
+    product_title = forms.CharField(required=False, label="Product name")
8
+    product_id = forms.CharField(required=False, label="Product ID")
9
+
10
+    status = forms.CharField(required=False, label="Shipping status")
11
+
12
+    date_formats = ('%d/%m/%Y',)
13
+    date_from = forms.DateField(required=False, label="Date from", input_formats=date_formats)
14
+    date_to = forms.DateField(required=False, label="Date to", input_formats=date_formats)    
15
+
16
+    voucher = forms.CharField(required=False, label="Voucher code")
17
+    payment_method = forms.CharField(label="Payment method", required=False)
18
+
19
+    format_choices = (('html', 'HTML'),
20
+                      ('csv', 'CSV'),)
21
+    response_format = forms.ChoiceField(widget=forms.RadioSelect, 
22
+            choices=format_choices, initial='html', label="Get results as")

+ 17
- 4
oscar/apps/dashboard/tests.py Просмотреть файл

@@ -19,16 +19,29 @@ class AnonymousUserTests(ViewTests):
19 19
         self.assertTrue('Username' in response.content)
20 20
 
21 21
 
22
-class StaffUserTests(ViewTests):
22
+class StaffViewTests(ViewTests):
23 23
 
24 24
     def setUp(self):
25
-        super(StaffUserTests, self).setUp()
25
+        super(StaffViewTests, self).setUp()
26 26
         user = User.objects.create_user('staffperson', 'staff@example.com', 'staffpassword')
27 27
         user.is_staff = True
28 28
         user.save()
29 29
         self.client.login(username='staffperson', password='staffpassword')
30 30
 
31
+
32
+class DashboardViewTests(StaffViewTests):
33
+
31 34
     def test_dashboard_index_is_for_staff_only(self):
32
-        response = self.client.get(reverse('dashboard:index'))
33
-        self.assertTrue('Username' not in response.content)
35
+        urls = ('dashboard:index',
36
+                'dashboard:orders',)
37
+        for name in urls:
38
+            response = self.client.get(reverse(name))
39
+            self.assertTrue('Username' not in response.content)
40
+
41
+
42
+class OrderListTests(StaffViewTests):
43
+
44
+    def test_searching_for_valid_order_number_redirects_to_order_page(self):
45
+        pass
46
+
34 47
 

+ 162
- 1
oscar/apps/dashboard/views.py Просмотреть файл

@@ -1,5 +1,166 @@
1
-from django.views.generic import TemplateView
1
+import csv
2
+
3
+from django.views.generic import TemplateView, ListView
4
+from django.contrib import messages
5
+from django.utils.datastructures import SortedDict
6
+from django.template.defaultfilters import date as format_date
7
+from django.db.models.loading import get_model
8
+from django.http import HttpResponse, HttpResponseRedirect
9
+from django.core.urlresolvers import reverse
10
+
11
+from oscar.apps.dashboard.forms import OrderSearchForm
12
+
13
+Order = get_model('order', 'Order')
2 14
 
3 15
 
4 16
 class IndexView(TemplateView):
5 17
     template_name = 'dashboard/index.html'
18
+
19
+
20
+class OrderListView(ListView):
21
+    model = Order
22
+    context_object_name = 'orders'
23
+    template_name = 'dashboard/orders/order_list.html'
24
+    form_class = OrderSearchForm
25
+    base_description = 'All orders'
26
+    paginate_by = 25
27
+    description = ''
28
+    actions = ('download_selected_orders',)
29
+
30
+    def get_queryset(self):
31
+        """
32
+        Build the queryset for this list and also update the title that 
33
+        describes the queryset
34
+        """
35
+        queryset = self.model.objects.all().order_by('-date_placed')
36
+        self.description = self.base_description
37
+
38
+        if 'order_number' not in self.request.GET:
39
+            self.form = self.form_class()
40
+            return queryset
41
+
42
+        self.form = self.form_class(self.request.GET)
43
+        if not self.form.is_valid():
44
+            return queryset
45
+
46
+        data = self.form.cleaned_data
47
+
48
+        if data['order_number']:
49
+            queryset = self.model.objects.filter(number__istartswith=data['order_number'])
50
+            self.description = 'Orders with number starting with "%s"' % data['order_number']
51
+
52
+        if data['name']:
53
+            # If the value is two words, then assume they are first name and last name
54
+            parts = data['name'].split()
55
+            if len(parts) == 2:
56
+                queryset = queryset.filter(Q(user__first_name__istartswith=parts[0]) |
57
+                                           Q(user__last_name__istartswith=parts[1])).distinct()
58
+            else:
59
+                queryset = queryset.filter(Q(user__first_name__istartswith=data['name']) |
60
+                                           Q(user__last_name__istartswith=data['name'])).distinct()
61
+            self.description += " with customer name matching '%s'" % data['name']
62
+
63
+        if data['product_title']:
64
+            queryset = queryset.filter(lines__title__istartswith=data['product_title']).distinct()
65
+            self.description += " including an item with title matching '%s'" % data['product_title']
66
+
67
+        if data['product_id']:
68
+            queryset = queryset.filter(Q(lines__upc=data['product_id']) |
69
+                                       Q(lines__product_id=data['product_id'])).distinct()
70
+            self.description += " including an item with ID '%s'" % data['product_id']
71
+
72
+        if data['date_from'] and data['date_to']:
73
+            # Add 24 hours to make search inclusive
74
+            date_to = data['date_to'] + datetime.timedelta(days=1)
75
+            queryset = queryset.filter(date_placed__gte=data['date_from']).filter(date_placed__lt=date_to)
76
+            self.description += " placed between %s and %s" % (format_date(data['date_from']), format_date(data['date_to']))
77
+        elif data['date_from']:
78
+            queryset = queryset.filter(date_placed__gte=data['date_from'])
79
+            self.description += " placed since %s" % format_date(data['date_from'])
80
+        elif data['date_to']:
81
+            date_to = data['date_to'] + datetime.timedelta(days=1)
82
+            queryset = queryset.filter(date_placed__lt=date_to)
83
+            self.description += " placed before %s" % format_date(data['date_to'])
84
+
85
+        if data['voucher']:
86
+            queryset = queryset.filter(discounts__voucher_code=data['voucher']).distinct()
87
+            self.description += " using voucher '%s'" % data['voucher']
88
+
89
+        if data['payment_method']:
90
+            queryset = queryset.filter(sources__source_type__code=data['payment_method']).distinct()
91
+            self.description += " paid for by %s" % data['payment_method']
92
+
93
+        if data['status']:
94
+            queryset = queryset.filter(status=data['status'])
95
+            self.description += " with status %s" % data['status']
96
+
97
+        return queryset
98
+
99
+    def get_context_data(self, **kwargs):
100
+        ctx = super(OrderListView, self).get_context_data(**kwargs)
101
+        ctx['queryset_description'] = self.description
102
+        ctx['form'] = self.form
103
+        return ctx
104
+
105
+    def is_csv_download(self):
106
+        return self.request.GET.get('response_format', None) == 'csv'
107
+
108
+    def get_paginate_by(self, queryset):
109
+        return None if self.is_csv_download() else self.paginate_by
110
+
111
+    def render_to_response(self, context):
112
+        if self.is_csv_download():
113
+            return self.download_selected_orders(self.request, context['object_list'])
114
+        return super(OrderListView, self).render_to_response(context)
115
+
116
+    def post(self, request, *args, **kwargs):
117
+        action = request.POST.get('action', '').lower()
118
+        if action not in self.actions:
119
+            messages.error(self.request, "Invalid action")
120
+            return HttpResponseRedirect(reverse('dashboard:orders'))
121
+        order_ids = request.POST.getlist('selected_order')
122
+        if not order_ids:
123
+            messages.error(self.request, "You need to select some orders")
124
+            return HttpResponseRedirect(reverse('dashboard:orders'))
125
+
126
+        raw_orders = Order.objects.in_bulk(order_ids)
127
+        orders = (raw_orders[int(id)] for id in order_ids)
128
+        return getattr(self, action)(request, orders)
129
+
130
+    def download_selected_orders(self, request, orders):
131
+        response = HttpResponse(mimetype='text/csv')
132
+        response['Content-Disposition'] = 'attachment; filename=orders.csv'
133
+        writer = csv.writer(response, delimiter=',')
134
+
135
+        meta_data = (('number', 'Order number'),
136
+                     ('value', 'Order value'),
137
+                     ('date', 'Date of purchase'),
138
+                     ('num_items', 'Number of items'),
139
+                     ('status', 'Order status'),
140
+                     ('shipping_address_name', 'Deliver to name'),
141
+                     ('billing_address_name', 'Bill to name'),
142
+                     )
143
+        columns = SortedDict()
144
+        for k,v in meta_data:
145
+            columns[k] = v
146
+
147
+        writer.writerow(columns.values())
148
+        for order in orders:
149
+            row = columns.copy()
150
+            row['number'] = order.number
151
+            row['value'] = order.total_incl_tax
152
+            row['date'] = order.date_placed
153
+            row['num_items'] = order.num_items
154
+            row['status'] = order.status
155
+            if order.shipping_address:
156
+                row['shipping_address_name'] = order.shipping_address.name()
157
+            else:
158
+                row['shipping_address_name'] = ''
159
+            if order.billing_address:
160
+                row['billing_address_name'] = order.billing_address.name()
161
+            else:
162
+                row['billing_address_name'] = ''
163
+
164
+            encoded_values = [unicode(value).encode('utf8') for value in row.values()]
165
+            writer.writerow(encoded_values)
166
+        return response

+ 91
- 0
oscar/templates/dashboard/orders/order_list.html Просмотреть файл

@@ -0,0 +1,91 @@
1
+{% extends "layout.html" %}
2
+
3
+{% load currency_filters %}
4
+
5
+{% block header %}
6
+<h2>Order management</h2>
7
+{% endblock header %}
8
+
9
+{% block content %}
10
+
11
+<h2>Search orders</h2>
12
+<form action="." method="get">
13
+	<table>
14
+		{{ form.as_table }}
15
+		<tr>
16
+			<th></th>
17
+			<td>
18
+				<input type="submit" value="Go!" />
19
+			</td>
20
+	</table>
21
+</form>
22
+
23
+<h2>{{ queryset_description }}</h2>
24
+{% if orders.count %}
25
+<form action="." method="post">
26
+    {% csrf_token %}
27
+<table>
28
+    <tr>
29
+        <th></th>
30
+        <th>Order number</th>
31
+        <th>Total inc tax</th>
32
+        <th>Date of purchase</th>
33
+        <th>Number of items</th>
34
+        <th>Shipping status</th>
35
+        <th>Customer</th>
36
+        <th>Shipping address</th>
37
+        <th>Billing address</th>
38
+        <th></th>
39
+    </tr>
40
+    {% for order in orders %}
41
+    <tr>
42
+        <td><input type="checkbox" name="selected_order" class="selected_order" value="{{ order.id }}"/>
43
+        <td>{{ order.number }}</td>
44
+        <td>{{ order.total_incl_tax|currency }}</td>
45
+		<td>{{ order.date_placed }}</td>
46
+		<td>{{ order.num_items }}</td>
47
+        <td>{{ order.shipping_status }}</td>
48
+        <td>{{ order.user|default:"Anonymous" }}</td>
49
+        <td>{{ order.shipping_address }}</td>
50
+        <td>{{ order.billing_address }}</td>
51
+        <td>
52
+            <a href="">View</a>
53
+        </td>
54
+    </tr>
55
+    {% endfor %}
56
+</table>
57
+
58
+{% if page_obj %}
59
+<div class="pagination">
60
+    <span class="step-links">
61
+    
62
+        {% if page_obj.has_previous %}
63
+            <a href="?page={{ page_obj.previous_page_number }}{{ search_params }}">previous</a>
64
+        {% endif %}
65
+
66
+        <span class="current">
67
+            Page {{ page_obj.number }} of {{ paginator.num_pages }}.
68
+        </span>
69
+
70
+        {% if page_obj.has_next %}
71
+            <a href="?page={{ page_obj.next_page_number }}{{ search_params }}">next</a>
72
+        {% endif %}
73
+    </span>
74
+</div>
75
+{% endif %}
76
+
77
+<p>With selected orders:</p>
78
+<ul>
79
+    <li><label><input type="radio" name="action" value="download_selected_orders" /> Download as CSV</label></li>
80
+</ul>
81
+<input type="submit" value="Go!" />
82
+
83
+</form>
84
+
85
+{% else %}
86
+
87
+<p>No orders found.</p>
88
+
89
+{% endif %}
90
+
91
+{% endblock content %}

Загрузка…
Отмена
Сохранить