mirror of
https://github.com/pretix/pretix.git
synced 2026-05-06 15:24:02 +00:00
Refs #145 -- Multi-use vouchers
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" %}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user