Refs #145 -- Multi-use vouchers

This commit is contained in:
Raphael Michel
2016-11-27 00:02:28 +01:00
parent 6c2ecd153c
commit db6fb51fc6
18 changed files with 470 additions and 104 deletions

View File

@@ -23,7 +23,7 @@ class VoucherForm(I18nModelForm):
localized_fields = '__all__'
fields = [
'code', 'valid_until', 'block_quota', 'allow_ignore_quota', 'price', 'tag',
'comment'
'comment', 'max_usages'
]
widgets = {
'valid_until': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),
@@ -81,11 +81,20 @@ class VoucherForm(I18nModelForm):
self.instance.item = None
self.instance.variation = None
if data['max_usages'] < self.instance.redeemed:
raise ValidationError(
_('This voucher has already been redeemed %(redeemed)s times. You cannot reduce the maximum number of '
'usages below this number.'),
params={
'redeemed': self.instance.redeemed
}
)
if 'codes' in data:
data['codes'] = [a.strip() for a in data.get('codes', '').strip().split("\n") if a]
cnt = len(data['codes'])
cnt = len(data['codes']) * data['max_usages']
else:
cnt = 1
cnt = data['max_usages']
if self._clean_quota_needs_checking(data):
self._clean_quota_check(data, cnt)
@@ -178,11 +187,18 @@ class VoucherBulkForm(VoucherForm):
model = Voucher
localized_fields = '__all__'
fields = [
'valid_until', 'block_quota', 'allow_ignore_quota', 'price', 'tag', 'comment'
'valid_until', 'block_quota', 'allow_ignore_quota', 'price', 'tag', 'comment',
'max_usages'
]
widgets = {
'valid_until': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),
}
labels = {
'max_usages': _('Maximum usages per voucher')
}
help_texts = {
'max_usages': _('Number of times times EACH of these vouchers can be redeemed.')
}
def clean(self):
data = super().clean()

View File

@@ -26,6 +26,7 @@
</div>
</div>
{% bootstrap_field form.codes layout="horizontal" %}
{% bootstrap_field form.max_usages layout="horizontal" %}
</fieldset>
<fieldset>
<legend>{% trans "Voucher details" %}</legend>

View File

@@ -23,6 +23,7 @@
<fieldset>
<legend>{% trans "Voucher details" %}</legend>
{% bootstrap_field form.code layout="horizontal" %}
{% bootstrap_field form.max_usages layout="horizontal" %}
{% bootstrap_field form.valid_until layout="horizontal" %}
{% bootstrap_field form.block_quota layout="horizontal" %}
{% bootstrap_field form.allow_ignore_quota layout="horizontal" %}

View File

@@ -54,7 +54,7 @@
<thead>
<tr>
<th>{% trans "Voucher code" %}</th>
<th>{% trans "Is redeemed" %}</th>
<th>{% trans "Redemptions" %}</th>
<th>{% trans "Expiry" %}</th>
<th>{% trans "Tag" %}</th>
<th>{% trans "Product" %}</th>
@@ -68,7 +68,7 @@
<strong><a href="
{% url "control:event.voucher" organizer=request.event.organizer.slug event=request.event.slug voucher=v.id %}">{{ v.code }}</a></strong>
</td>
<td>{% if v.redeemed %}{% trans "Yes" %}{% else %}{% trans "No" %}{% endif %}</td>
<td>{{ v.redeemed }} / {{ v.max_usages }}</td>
<td>{{ v.valid_until|date }}</td>
<td>
{{ v.tag }}

View File

@@ -5,7 +5,7 @@ from django.conf import settings
from django.contrib import messages
from django.core.urlresolvers import resolve, reverse
from django.db import transaction
from django.db.models import Case, Count, IntegerField, Q, Sum, When
from django.db.models import Count, Q, Sum
from django.http import (
Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect,
JsonResponse,
@@ -41,11 +41,11 @@ class VoucherList(EventPermissionRequiredMixin, ListView):
if self.request.GET.get("status", "") != "":
s = self.request.GET.get("status", "")
if s == 'v':
qs = qs.filter(Q(valid_until__isnull=True) | Q(valid_until__gt=now())).filter(redeemed=False)
qs = qs.filter(Q(valid_until__isnull=True) | Q(valid_until__gt=now())).filter(redeemed=0)
elif s == 'r':
qs = qs.filter(redeemed=True)
qs = qs.filter(redeemed__gt=0)
elif s == 'e':
qs = qs.filter(Q(valid_until__isnull=False) & Q(valid_until__lt=now())).filter(redeemed=False)
qs = qs.filter(Q(valid_until__isnull=False) & Q(valid_until__lt=now())).filter(redeemed=0)
return qs
def get(self, request, *args, **kwargs):
@@ -59,7 +59,7 @@ class VoucherList(EventPermissionRequiredMixin, ListView):
headers = [
_('Voucher code'), _('Valid until'), _('Product'), _('Reserve quota'), _('Bypass quota'),
_('Price'), _('Tag'), _('Redeemed')
_('Price'), _('Tag'), _('Redeemed'), _('Maximum usages')
]
writer.writerow(headers)
@@ -79,7 +79,8 @@ class VoucherList(EventPermissionRequiredMixin, ListView):
_("Yes") if v.allow_ignore_quota else _("No"),
str(v.price) if v.price else "",
v.tag,
_("Yes") if v.redeemed else _("No"),
str(v.redeemed),
str(v.max_usages)
]
writer.writerow(row)
@@ -97,14 +98,7 @@ class VoucherTags(EventPermissionRequiredMixin, TemplateView):
tags = self.request.event.vouchers.order_by('tag').filter(tag__isnull=False).values('tag').annotate(
total=Count('id'),
# This is a fix for this MySQL issue: https://code.djangoproject.com/ticket/24662
redeemed=Sum(
Case(
When(redeemed=True, then=1),
When(redeemed=False, then=0),
output_field=IntegerField()
)
)
redeemed=Sum('redeemed')
)
for t in tags:
t['percentage'] = int((t['redeemed'] / t['total']) * 100)
@@ -128,7 +122,7 @@ class VoucherDelete(EventPermissionRequiredMixin, DeleteView):
raise Http404(_("The requested voucher does not exist."))
def get(self, request, *args, **kwargs):
if self.get_object().redeemed:
if self.get_object().redeemed > 0:
messages.error(request, _('A voucher can not be deleted if it already has been redeemed.'))
return HttpResponseRedirect(self.get_success_url())
return super().get(request, *args, **kwargs)
@@ -138,7 +132,7 @@ class VoucherDelete(EventPermissionRequiredMixin, DeleteView):
self.object = self.get_object()
success_url = self.get_success_url()
if self.object.redeemed:
if self.object.redeemed > 0:
messages.error(request, _('A voucher can not be deleted if it already has been redeemed.'))
else:
self.object.log_action('pretix.voucher.deleted', user=self.request.user)