Bläddra i källkod

Voucher dashboard now in place.

Features:
* Basic crud
* Stats view
master
David Winterbottom 13 år sedan
förälder
incheckning
87fd5c64c5

+ 1
- 0
oscar/__init__.py Visa fil

@@ -56,6 +56,7 @@ OSCAR_CORE_APPS = [
56 56
     'oscar.apps.dashboard.catalogue',
57 57
     'oscar.apps.dashboard.offers',
58 58
     'oscar.apps.dashboard.ranges',
59
+    'oscar.apps.dashboard.vouchers',
59 60
 ]
60 61
 
61 62
 

+ 3
- 0
oscar/apps/basket/forms.py Visa fil

@@ -57,6 +57,9 @@ class BasketVoucherForm(forms.Form):
57 57
     def __init__(self, *args, **kwargs):
58 58
         return super(BasketVoucherForm, self).__init__(*args,**kwargs)
59 59
 
60
+    def clean_code(self):
61
+        return self.cleaned_data['code'].strip().upper()
62
+
60 63
 
61 64
 class ProductSelectionForm(forms.Form):
62 65
     product_id = forms.IntegerField(min_value=1)

+ 3
- 0
oscar/apps/dashboard/app.py Visa fil

@@ -11,6 +11,7 @@ from oscar.apps.dashboard.pages.app import application as pages_app
11 11
 from oscar.apps.dashboard.offers.app import application as offers_app
12 12
 from oscar.apps.dashboard.ranges.app import application as ranges_app
13 13
 from oscar.apps.dashboard.reviews.app import application as reviews_app
14
+from oscar.apps.dashboard.vouchers.app import application as vouchers_app
14 15
 from oscar.apps.dashboard import views
15 16
 
16 17
 
@@ -27,6 +28,7 @@ class DashboardApplication(Application):
27 28
     offers_app = offers_app
28 29
     ranges_app = ranges_app
29 30
     reviews_app = reviews_app
31
+    vouchers_app = vouchers_app
30 32
 
31 33
     def get_urls(self):
32 34
         urlpatterns = patterns('',
@@ -40,6 +42,7 @@ class DashboardApplication(Application):
40 42
             url(r'^offers/', include(self.offers_app.urls)),
41 43
             url(r'^ranges/', include(self.ranges_app.urls)),
42 44
             url(r'^reviews/', include(self.reviews_app.urls)),
45
+            url(r'^vouchers/', include(self.vouchers_app.urls)),
43 46
         )
44 47
         return self.post_process_urls(urlpatterns)
45 48
 

+ 1
- 1
oscar/apps/dashboard/offers/views.py Visa fil

@@ -29,7 +29,7 @@ class OfferListView(ListView):
29 29
     form_class = OfferSearchForm
30 30
 
31 31
     def get_queryset(self):
32
-        qs = self.model._default_manager.all()
32
+        qs = self.model._default_manager.filter(offer_type=ConditionalOffer.SITE)
33 33
         self.description = "All offers"
34 34
 
35 35
         self.form = self.form_class(self.request.GET)

+ 1
- 1
oscar/apps/dashboard/orders/app.py Visa fil

@@ -8,7 +8,7 @@ from oscar.apps.dashboard.nav import register, Node
8 8
 node = Node('Orders')
9 9
 node.add_child(Node('Orders', 'dashboard:order-list'))
10 10
 node.add_child(Node('Statistics', 'dashboard:order-stats'))
11
-register(node, 60)
11
+register(node, 80)
12 12
 
13 13
 
14 14
 class OrdersDashboardApplication(Application):

+ 1
- 1
oscar/apps/dashboard/ranges/app.py Visa fil

@@ -7,7 +7,7 @@ from oscar.apps.dashboard.nav import register, Node
7 7
 
8 8
 node = Node('Ranges')
9 9
 node.add_child(Node('All ranges', 'dashboard:range-list'))
10
-register(node, 55)
10
+register(node, 70)
11 11
 
12 12
 
13 13
 class RangeDashboardApplication(Application):

+ 1
- 1
oscar/apps/dashboard/reports/app.py Visa fil

@@ -6,7 +6,7 @@ from oscar.apps.dashboard.reports import views
6 6
 from oscar.apps.dashboard.nav import register, Node
7 7
 
8 8
 node = Node('Reports', 'dashboard:reports-index')
9
-register(node, 70)
9
+register(node, 90)
10 10
 
11 11
 
12 12
 class ReportsApplication(Application):

+ 1
- 1
oscar/apps/dashboard/reviews/app.py Visa fil

@@ -6,7 +6,7 @@ from oscar.apps.dashboard.nav import register, Node
6 6
 from oscar.apps.dashboard.reviews import views
7 7
 
8 8
 node = Node('Reviews', 'dashboard:reviews-list')
9
-register(node, 80)
9
+register(node, 35)
10 10
 
11 11
 
12 12
 class ReviewsApplication(Application):

+ 1
- 2
oscar/apps/dashboard/views.py Visa fil

@@ -7,7 +7,6 @@ from django.db.models import Avg, Sum, Count
7 7
 from django.contrib.auth.models import User
8 8
 
9 9
 from oscar.apps.basket.abstract_models import OPEN as basket_OPEN
10
-from oscar.apps.offer.models import SITE
11 10
 from oscar.apps.promotions.models import AbstractPromotion
12 11
 
13 12
 ConditionalOffer = get_model('offer', 'ConditionalOffer')
@@ -34,7 +33,7 @@ class IndexView(TemplateView):
34 33
         the current date.
35 34
         """
36 35
         return ConditionalOffer.objects.filter(end_date__gt=datetime.now(),
37
-                                               offer_type=SITE)
36
+                                               offer_type=ConditionalOffer.SITE)
38 37
 
39 38
     def get_active_vouchers(self):
40 39
         """

+ 0
- 0
oscar/apps/dashboard/vouchers/__init__.py Visa fil


+ 35
- 0
oscar/apps/dashboard/vouchers/app.py Visa fil

@@ -0,0 +1,35 @@
1
+from django.conf.urls.defaults import patterns, url
2
+from django.contrib.admin.views.decorators import staff_member_required
3
+
4
+from oscar.core.application import Application
5
+from oscar.apps.dashboard.vouchers import views
6
+from oscar.apps.dashboard.nav import register, Node
7
+
8
+node = Node('Vouchers', 'dashboard:voucher-list')
9
+register(node, 60)
10
+
11
+
12
+class VoucherDashboardApplication(Application):
13
+    name = None
14
+
15
+    list_view = views.VoucherListView
16
+    create_view = views.VoucherCreateView
17
+    update_view = views.VoucherUpdateView
18
+    delete_view = views.VoucherDeleteView
19
+    stats_view = views.VoucherStatsView
20
+
21
+    def get_urls(self):
22
+        urlpatterns = patterns('',
23
+            url(r'^$', self.list_view.as_view(), name='voucher-list'),
24
+            url(r'^create/$', self.create_view.as_view(), name='voucher-create'),
25
+            url(r'^update/(?P<pk>\d+)/$', self.update_view.as_view(), name='voucher-update'),
26
+            url(r'^delete/(?P<pk>\d+)/$', self.delete_view.as_view(), name='voucher-delete'),
27
+            url(r'^stats/(?P<pk>\d+)/$', self.stats_view.as_view(), name='voucher-stats'),
28
+        )
29
+        return self.post_process_urls(urlpatterns)
30
+
31
+    def get_url_decorator(self, url_name):
32
+        return staff_member_required
33
+
34
+
35
+application = VoucherDashboardApplication()

+ 58
- 0
oscar/apps/dashboard/vouchers/forms.py Visa fil

@@ -0,0 +1,58 @@
1
+from django import forms
2
+from django.db.models.loading import get_model
3
+
4
+Voucher = get_model('voucher', 'Voucher')
5
+Benefit = get_model('offer', 'Benefit')
6
+Range = get_model('offer', 'Range')
7
+
8
+
9
+class VoucherForm(forms.Form):
10
+    """
11
+    A specialised form for creating a voucher and offer
12
+    model.
13
+    """
14
+    name = forms.CharField(label="Name")
15
+    code = forms.CharField(label="Code")
16
+    start_date = forms.DateField(label="Start date")
17
+    end_date = forms.DateField(label="End date")
18
+    usage = forms.ChoiceField(choices=Voucher.USAGE_CHOICES)
19
+
20
+    benefit_range = forms.ModelChoiceField(
21
+        label='Which products get a discount?',
22
+        queryset=Range.objects.all(),
23
+    )
24
+    type_choices = (
25
+        (Benefit.PERCENTAGE, '% off products in range'),
26
+        (Benefit.FIXED, 'Fixed amount off products in range'),
27
+    )
28
+    benefit_type = forms.ChoiceField(
29
+        choices=type_choices,
30
+        label='Discount type'
31
+    )
32
+    benefit_value = forms.DecimalField(
33
+        label='Discount value')
34
+
35
+    def __init__(self, voucher=None, *args, **kwargs):
36
+        self.voucher = voucher
37
+        super(VoucherForm, self).__init__(*args, **kwargs)
38
+
39
+    def clean_code(self):
40
+        code = self.cleaned_data['code'].strip().upper()
41
+        if not code:
42
+            raise forms.ValidationError("Please enter a voucher code")
43
+        try:
44
+            voucher = Voucher.objects.get(code=code)
45
+        except Voucher.DoesNotExist:
46
+            pass
47
+        else:
48
+            if voucher.id != self.voucher.id:
49
+                raise forms.ValidationError("The code '%s' is already in use" % code)
50
+        return code
51
+
52
+    def clean(self):
53
+        cleaned_data = super(VoucherForm, self).clean()
54
+        start_date = cleaned_data['start_date']
55
+        end_date = cleaned_data['end_date']
56
+        if end_date < start_date:
57
+            raise forms.ValidationError("The start date must be before the end date")
58
+        return cleaned_data

+ 272
- 0
oscar/apps/dashboard/vouchers/views.py Visa fil

@@ -0,0 +1,272 @@
1
+from django.views.generic import (ListView, FormView, DetailView, DeleteView)
2
+from django.db.models.loading import get_model
3
+from django.http import HttpResponseRedirect
4
+from django.contrib import messages
5
+from django.core.urlresolvers import reverse
6
+
7
+from oscar.core.loading import get_class
8
+VoucherForm = get_class('dashboard.vouchers.forms', 'VoucherForm')
9
+
10
+Voucher = get_model('voucher', 'Voucher')
11
+ConditionalOffer = get_model('offer', 'ConditionalOffer')
12
+Benefit = get_model('offer', 'Benefit')
13
+Condition = get_model('offer', 'Condition')
14
+OrderDiscount = get_model('order', 'OrderDiscount')
15
+
16
+
17
+class VoucherListView(ListView):
18
+    model = Voucher
19
+    context_object_name = 'vouchers'
20
+    template_name = 'dashboard/vouchers/voucher_list.html'
21
+
22
+
23
+class VoucherCreateView(FormView):
24
+    model = Voucher
25
+    template_name = 'dashboard/vouchers/voucher_form.html'
26
+    form_class = VoucherForm
27
+
28
+    def get_context_data(self, **kwargs):
29
+        ctx = super(VoucherCreateView, self).get_context_data(**kwargs)
30
+        ctx['title'] = 'Create voucher'
31
+        return ctx
32
+
33
+    def form_valid(self, form):
34
+        # Create offer and benefit
35
+        condition = Condition.objects.create(
36
+            range=form.cleaned_data['benefit_range'],
37
+            type=Condition.COUNT,
38
+            value=1
39
+        )
40
+        benefit = Benefit.objects.create(
41
+            range=form.cleaned_data['benefit_range'],
42
+            type=form.cleaned_data['benefit_type'],
43
+            value=form.cleaned_data['benefit_value']
44
+        )
45
+        name = form.cleaned_data['name']
46
+        offer = ConditionalOffer.objects.create(
47
+            name="Offer for voucher '%s'" % name,
48
+            offer_type="Voucher",
49
+            benefit=benefit,
50
+            condition=condition,
51
+        )
52
+        voucher = Voucher.objects.create(
53
+            name=name,
54
+            code=form.cleaned_data['code'],
55
+            usage=form.cleaned_data['usage'],
56
+            start_date=form.cleaned_data['start_date'],
57
+            end_date=form.cleaned_data['end_date'],
58
+        )
59
+        voucher.offers.add(offer)
60
+        return HttpResponseRedirect(self.get_success_url())
61
+
62
+    def get_success_url(self):
63
+        messages.success(self.request, "Voucher created")
64
+        return reverse('dashboard:voucher-list')
65
+
66
+
67
+class VoucherStatsView(DetailView):
68
+    model = Voucher
69
+    template_name = 'dashboard/vouchers/voucher_detail.html'
70
+    context_object_name = 'voucher'
71
+
72
+    def get_context_data(self, **kwargs):
73
+        ctx = super(VoucherStatsView, self).get_context_data(**kwargs)
74
+        ctx['discounts'] = OrderDiscount.objects.filter(voucher_id=self.object.id).order_by('-order__date_placed')
75
+        return ctx
76
+
77
+
78
+class VoucherUpdateView(FormView):
79
+    model = Voucher
80
+    template_name = 'dashboard/vouchers/voucher_form.html'
81
+    form_class = VoucherForm
82
+
83
+    def get_voucher(self):
84
+        if not hasattr(self, 'voucher'):
85
+            self.voucher = Voucher.objects.get(id=self.kwargs['pk'])
86
+        return self.voucher
87
+
88
+    def get_context_data(self, **kwargs):
89
+        ctx = super(VoucherUpdateView, self).get_context_data(**kwargs)
90
+        ctx['title'] = 'Update voucher'
91
+        return ctx
92
+
93
+    def get_form_kwargs(self):
94
+        kwargs = super(VoucherUpdateView, self).get_form_kwargs()
95
+        kwargs['voucher'] = self.get_voucher()
96
+        return kwargs
97
+
98
+    def get_initial(self):
99
+        voucher = self.get_voucher()
100
+        offer = voucher.offers.all()[0]
101
+        benefit = offer.benefit
102
+        return {
103
+            'name': voucher.name,
104
+            'code': voucher.code,
105
+            'start_date': voucher.start_date,
106
+            'end_date': voucher.end_date,
107
+            'usage': voucher.usage,
108
+            'benefit_type': benefit.type,
109
+            'benefit_range': benefit.range,
110
+            'benefit_value': benefit.value,
111
+        }
112
+
113
+    def form_valid(self, form):
114
+        voucher = self.get_voucher()
115
+        voucher.name = form.cleaned_data['name']
116
+        voucher.code = form.cleaned_data['code']
117
+        voucher.usage = form.cleaned_data['usage']
118
+        voucher.start_date = form.cleaned_data['start_date']
119
+        voucher.end_date = form.cleaned_data['end_date']
120
+        voucher.save()
121
+
122
+        offer = voucher.offers.all()[0]
123
+        offer.condition.range = form.cleaned_data['benefit_range']
124
+        offer.condition.save()
125
+
126
+        benefit = voucher.benefit
127
+        benefit.range = form.cleaned_data['benefit_range']
128
+        benefit.type = form.cleaned_data['benefit_type']
129
+        benefit.value = form.cleaned_data['benefit_value']
130
+        benefit.save()
131
+
132
+        return HttpResponseRedirect(self.get_success_url())
133
+
134
+    def get_success_url(self):
135
+        messages.success(self.request, "Voucher updated")
136
+        return reverse('dashboard:voucher-list')
137
+
138
+
139
+class VoucherDeleteView(DeleteView):
140
+    model = Voucher
141
+    template_name = 'dashboard/vouchers/voucher_delete.html'
142
+    context_object_name = 'voucher'
143
+
144
+    def get_success_url(self):
145
+        messages.warning(self.request, "Voucher deleted")
146
+        return reverse('dashboard:voucher-list')
147
+
148
+
149
+"""
150
+class RangeUpdateView(UpdateView):
151
+    model = Range
152
+    template_name = 'dashboard/ranges/range_form.html'
153
+    form_class = RangeForm
154
+
155
+    def get_success_url(self):
156
+        messages.success(self.request, "Range updated")
157
+        return reverse('dashboard:range-list')
158
+
159
+
160
+class RangeDeleteView(DeleteView):
161
+    model = Range
162
+    template_name = 'dashboard/ranges/range_delete.html'
163
+    context_object_name = 'range'
164
+
165
+    def get_success_url(self):
166
+        messages.warning(self.request, "Range deleted")
167
+        return reverse('dashboard:range-list')
168
+
169
+
170
+class RangeProductListView(ListView, BulkEditMixin):
171
+    model = Product
172
+    template_name = 'dashboard/ranges/range_product_list.html'
173
+    context_object_name = 'products'
174
+    actions = ('remove_selected_products', 'add_products')
175
+    form_class = RangeProductForm
176
+
177
+    def post(self, request, *args, **kwargs):
178
+        self.object_list = self.get_queryset()
179
+        if request.POST.get('action', None) == 'add_products':
180
+            return self.add_products(request)
181
+        return super(RangeProductListView, self).post(request, *args, **kwargs)
182
+
183
+    def get_range(self):
184
+        if not hasattr(self, '_range'):
185
+            self._range = get_object_or_404(Range, id=self.kwargs['pk'])
186
+        return self._range
187
+
188
+    def get_queryset(self):
189
+        return self.get_range().included_products.all()
190
+
191
+    def get_context_data(self, **kwargs):
192
+        ctx = super(RangeProductListView, self).get_context_data(**kwargs)
193
+        range = self.get_range()
194
+        ctx['range'] = range
195
+        if 'form' not in ctx:
196
+            ctx['form'] = self.form_class(range)
197
+        return ctx
198
+
199
+    def remove_selected_products(self, request, products):
200
+        range = self.get_range()
201
+        for product in products:
202
+            range.included_products.remove(product)
203
+        messages.success(request, 'Removed %d products from range' %
204
+                         len(products))
205
+        return HttpResponseRedirect(self.get_success_url(request))
206
+
207
+    def add_products(self, request):
208
+        range = self.get_range()
209
+        form = self.form_class(range, request.POST, request.FILES)
210
+        if not form.is_valid():
211
+            ctx = self.get_context_data(form=form, object_list=self.object_list)
212
+            return self.render_to_response(ctx)
213
+
214
+        self.handle_query_products(request, range, form)
215
+        self.handle_file_products(request, range, form)
216
+        return HttpResponseRedirect(self.get_success_url(request))
217
+
218
+    def handle_query_products(self, request, range, form):
219
+        products = form.get_products()
220
+        if not products:
221
+            return
222
+
223
+        for product in products:
224
+            range.included_products.add(product)
225
+
226
+        num_products = len(products)
227
+        messages.success(request, "%d product%s added to range" % (
228
+            num_products, pluralize(num_products)))
229
+
230
+        dupe_skus = form.get_duplicate_skus()
231
+        if dupe_skus:
232
+            messages.warning(
233
+                request,
234
+                "The products with SKUs or UPCs matching %s are already in this range" % (", ".join(dupe_skus)))
235
+
236
+        missing_skus = form.get_missing_skus()
237
+        if missing_skus:
238
+            messages.warning(request,
239
+                             "No product was found with SKU or UPC matching %s" % ', '.join(missing_skus))
240
+
241
+    def handle_file_products(self, request, range, form):
242
+        if not 'file_upload' in request.FILES:
243
+            return 
244
+        upload = self.create_upload_object(request, range)
245
+        upload.process()
246
+        if not upload.was_processing_successful():
247
+            messages.error(request, upload.error_message)
248
+        else:
249
+            msg = "File processed: %d products added, %d duplicate identifiers, %d " \
250
+                  "identifiers were not found"
251
+            msg = msg % (upload.num_new_skus, upload.num_duplicate_skus,
252
+                         upload.num_unknown_skus)
253
+            if upload.num_new_skus:
254
+                messages.success(request, msg)
255
+            else:
256
+                messages.warning(request, msg)
257
+        upload.delete_file()
258
+
259
+    def create_upload_object(self, request, range):
260
+        f = request.FILES['file_upload']
261
+        destination_path = os.path.join(settings.OSCAR_UPLOAD_ROOT, f.name)
262
+        with open(destination_path, 'wb+') as dest:
263
+            for chunk in f.chunks():
264
+                dest.write(chunk)
265
+        upload = RangeProductFileUpload.objects.create(
266
+            range=range,
267
+            uploaded_by=request.user,
268
+            filepath=destination_path,
269
+            size=f.size
270
+        )
271
+        return upload
272
+    """

+ 1
- 2
oscar/apps/offer/models.py Visa fil

@@ -13,8 +13,6 @@ from django.conf import settings
13 13
 from oscar.apps.offer.managers import ActiveOfferManager
14 14
 from oscar.models.fields import PositiveDecimalField, ExtendedURLField
15 15
 
16
-SITE, VOUCHER, USER, SESSION = ("Site", "Voucher", "User", "Session")
17
-
18 16
 
19 17
 class ConditionalOffer(models.Model):
20 18
     """
@@ -35,6 +33,7 @@ class ConditionalOffer(models.Model):
35 33
     #     to apply this offer needs to be coded
36 34
     # (d) Session offers - these are temporarily available to a user after some trigger 
37 35
     #     event.  Eg, users coming from some affiliate site get 10% off.     
36
+    SITE, VOUCHER, USER, SESSION = ("Site", "Voucher", "User", "Session")
38 37
     TYPE_CHOICES = (
39 38
         (SITE, "Site offer - available to all users"),
40 39
         (VOUCHER, "Voucher offer - only available after entering the appropriate voucher code"),

+ 4
- 0
oscar/apps/voucher/abstract_models.py Visa fil

@@ -91,6 +91,10 @@ class AbstractVoucher(models.Model):
91 91
         Records a usage of this voucher in an order.
92 92
         """
93 93
         self.applications.create(voucher=self, order=order, user=user)
94
+
95
+    @property
96
+    def benefit(self):
97
+        return self.offers.all()[0].benefit
94 98
         
95 99
         
96 100
 class AbstractVoucherApplication(models.Model):

+ 50
- 0
oscar/templates/dashboard/vouchers/voucher_delete.html Visa fil

@@ -0,0 +1,50 @@
1
+{% extends 'dashboard/layout.html' %}
2
+{% load currency_filters %}
3
+
4
+{% block title %}
5
+Delete voucher '{{ voucher.name }}'? | Vouchers | {{ block.super }}
6
+{% endblock %}
7
+
8
+{% block breadcrumbs %}
9
+<ul class="breadcrumb">
10
+    <li>
11
+        <a href="{% url dashboard:index %}">Dashboard</a>
12
+        <span class="divider">/</span>
13
+    </li>
14
+    <li>
15
+        <a href="{% url dashboard:voucher-list %}">Vouchers</a>
16
+        <span class="divider">/</span>
17
+    </li>
18
+	<li class="active"><a href=".">Delete voucher '{{ voucher.name }}'?</a></li>
19
+</ul>
20
+{% endblock %}
21
+
22
+{% block header %}
23
+<div class="page-header">
24
+	<h1>Delete voucher '{{ voucher.name }}'?</h1>
25
+</div>
26
+{% endblock header %}
27
+
28
+{% block dashboard_content %}
29
+
30
+    <h2>Voucher details</h2>
31
+	<table class="table">
32
+		<tbody>
33
+			<tr><th>Name</th><td>{{ voucher.name }}</td></tr>
34
+			<tr><th>Code</th><td>{{ voucher.code }}</td></tr>
35
+			<tr><th>Start date</th><td>{{ voucher.start_date }}</td></tr>
36
+			<tr><th>End date</th><td>{{ voucher.end_date }}</td></tr>
37
+			<tr><th>Usage</th><td>{{ voucher.usage }}</td></tr>
38
+			<tr><th>Discount</th><td>{{ voucher.benefit_description }}</td></tr>
39
+		</tbody>
40
+	</table>
41
+
42
+	<form action="." method="post" class="">
43
+		{% csrf_token %}
44
+		<div class="form-actions">
45
+			<button class="btn btn-danger btn-large" type="submit">Delete</button> or
46
+			<a href="{% url dashboard:range-list %}">cancel</a>
47
+		</div>
48
+	</form>
49
+
50
+{% endblock dashboard_content %}

+ 86
- 0
oscar/templates/dashboard/vouchers/voucher_detail.html Visa fil

@@ -0,0 +1,86 @@
1
+{% extends 'dashboard/layout.html' %}
2
+{% load currency_filters %}
3
+
4
+{% block title %}
5
+Voucher '{{ voucher.name }}' | Vouchers | {{ block.super }}
6
+{% endblock %}
7
+
8
+{% block breadcrumbs %}
9
+<ul class="breadcrumb">
10
+    <li>
11
+        <a href="{% url dashboard:index %}">Dashboard</a>
12
+        <span class="divider">/</span>
13
+    </li>
14
+    <li>
15
+        <a href="{% url dashboard:voucher-list %}">Vouchers</a>
16
+        <span class="divider">/</span>
17
+    </li>
18
+	<li class="active"><a href=".">Voucher '{{ voucher.name }}'</a></li>
19
+</ul>
20
+{% endblock %}
21
+
22
+{% block header %}
23
+<div class="page-header">
24
+	<h1>Voucher '{{ voucher.name }}'</h1>
25
+</div>
26
+{% endblock header %}
27
+
28
+{% block dashboard_content %}
29
+
30
+    <h2>Voucher details</h2>
31
+	<table class="table">
32
+		<tbody>
33
+			<tr><th>Name</th><td>{{ voucher.name }}</td></tr>
34
+			<tr><th>Code</th><td>{{ voucher.code }}</td></tr>
35
+			<tr><th>Start date</th><td>{{ voucher.start_date }}</td></tr>
36
+			<tr><th>End date</th><td>{{ voucher.end_date }}</td></tr>
37
+			<tr><th>Usage</th><td>{{ voucher.usage }}</td></tr>
38
+			<tr><th>Discount</th><td>{{ voucher.benefit.description }}</td></tr>
39
+		</tbody>
40
+	</table>
41
+
42
+    <h2>Voucher performance</h2>
43
+	<table class="table">
44
+		<tbody>
45
+			<tr><th>Number of basket additions</th><td>{{ voucher.num_basket_additions }}</td></tr>
46
+			<tr><th>Number of orders</th><td>{{ voucher.num_orders }}</td></tr>
47
+			<tr><th>Total discount</th><td>{{ voucher.total_discount }}</td></tr>
48
+		</tbody>
49
+	</table>
50
+
51
+    <h2>Recent orders</h2>
52
+	{% if not discounts %}
53
+	<p>No orders have been placed that use this voucher.</p>
54
+	{% else %}
55
+	<table class="table">
56
+		<thead>
57
+			<th>Order number</th>
58
+			<th>Order total</th>
59
+			<th>Discount</th>
60
+			<th>Date placed</th>
61
+			<th></th>
62
+		</thead>
63
+		<tbody>
64
+			{% for discount in discounts %}
65
+			{% with order=discount.order %}
66
+				<tr>
67
+					<td>{{ order.number }}</td>
68
+					<td>{{ order.total_incl_tax|currency }}</td>
69
+					<td>{{ discount.amount|currency }}</td>
70
+					<td>{{ order.date_placed }}</td>
71
+					<td><a href="{% url dashboard:order-detail order.number %}" class="btn btn-info">View</a></td>
72
+				</tr>
73
+			{% endwith %}
74
+			{% endfor %}
75
+		</tbody>
76
+	</table>
77
+	{% endif %}
78
+
79
+	<div class="form-actions">
80
+		<a class="btn btn-primary" href="{% url dashboard:voucher-update voucher.id %}">Edit</a> or
81
+		<a class="btn btn-danger" href="{% url dashboard:voucher-delete voucher.id %}">Delete</a> or
82
+		<a href="{% url dashboard:voucher-list %}">cancel</a>
83
+	</div>
84
+
85
+
86
+{% endblock dashboard_content %}

+ 39
- 0
oscar/templates/dashboard/vouchers/voucher_form.html Visa fil

@@ -0,0 +1,39 @@
1
+{% extends 'dashboard/layout.html' %}
2
+{% load currency_filters %}
3
+
4
+{% block title %}
5
+{{ title }} | Vouchers | {{ block.super }}
6
+{% endblock %}
7
+
8
+{% block breadcrumbs %}
9
+<ul class="breadcrumb">
10
+    <li>
11
+        <a href="{% url dashboard:index %}">Dashboard</a>
12
+        <span class="divider">/</span>
13
+    </li>
14
+    <li>
15
+        <a href="{% url dashboard:voucher-list %}">Vouchers</a>
16
+        <span class="divider">/</span>
17
+    </li>
18
+	<li class="active"><a href=".">{{ title }}</a></li>
19
+</ul>
20
+{% endblock %}
21
+
22
+{% block header %}
23
+<div class="page-header">
24
+	<h1>{{ title }}</h1>
25
+</div>
26
+{% endblock header %}
27
+
28
+{% block dashboard_content %}
29
+<form action="." method="post" class="well form-horizontal">
30
+	{% csrf_token %}
31
+	{% include "partials/form_fields.html" with form=form %}
32
+	{% block form_actions %}
33
+	<div class="form-actions">
34
+		<button class="btn btn-primary btn-large" type="submit">Save</button> or
35
+		<a href="{% url dashboard:range-list %}">cancel</a>
36
+	</div>
37
+	{% endblock form_actions %}
38
+</form>
39
+{% endblock dashboard_content %}

+ 62
- 0
oscar/templates/dashboard/vouchers/voucher_list.html Visa fil

@@ -0,0 +1,62 @@
1
+{% extends 'dashboard/layout.html' %}
2
+{% load currency_filters %}
3
+
4
+{% block title %}
5
+Vouchers | {{ block.super }}
6
+{% endblock %}
7
+
8
+{% block breadcrumbs %}
9
+<ul class="breadcrumb">
10
+    <li>
11
+        <a href="{% url dashboard:index %}">Dashboard</a>
12
+        <span class="divider">/</span>
13
+    </li>
14
+    <li class="active"><a href=".">Vouchers</a></li>
15
+</ul>
16
+{% endblock %}
17
+
18
+{% block header %}
19
+<div class="page-header">
20
+    <h1>Vouchers</h1>
21
+</div>
22
+{% endblock header %}
23
+
24
+{% block dashboard_content %}
25
+
26
+<p><a href="{% url dashboard:voucher-create %} " class="btn-large btn-primary">Create new voucher</a></p>
27
+
28
+	<div class="sub-header">
29
+		<h2>All vouchers</h2>
30
+	</div>
31
+	{% if vouchers.count %}
32
+		<table class="table table-striped table-bordered">
33
+			<tr>
34
+				<th>Name</th>
35
+				<th>Code</th>
36
+				<th>Num baskets</th>
37
+				<th>Num orders</th>
38
+				<th>Date created</th>
39
+				<th></th>
40
+			</tr>
41
+			{% for voucher in vouchers %}
42
+			<tr>
43
+				<td>{{ voucher.name }}</td>
44
+				<td>{{ voucher.code }}</td>
45
+				<td>{{ voucher.num_basket_additions }}</td>
46
+				<td>{{ voucher.num_orders }}</td>
47
+				<td>{{ voucher.date_created }}</td>
48
+				<td>
49
+					<a class="btn btn-info" href="{% url dashboard:voucher-stats voucher.id %}">Stats</a>
50
+					<a class="btn btn-primary" href="{% url dashboard:voucher-update voucher.id %}">Edit</a>
51
+					<a class="btn btn-danger" href="">Delete</a>
52
+				</td>
53
+			</tr>
54
+			{% endfor %}
55
+		</table>
56
+		{% if page_obj %}
57
+			{% include "catalogue/partials/pagination.html" %}
58
+		{% endif %}
59
+	{% else %}
60
+		<p>No vouchers found.</p>
61
+	{% endif %}
62
+{% endblock dashboard_content %}

Laddar…
Avbryt
Spara