Fix #782 -- Select2 widget for item selection for vouchers

This commit is contained in:
Raphael Michel
2018-04-03 12:10:34 +02:00
parent 7ec5adb6b4
commit bb10d25561
6 changed files with 163 additions and 36 deletions

View File

@@ -1,20 +1,25 @@
import copy
from django import forms
from django.core.exceptions import ValidationError
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db.models.functions import Lower
from django.urls import reverse
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from pretix.base.forms import I18nModelForm
from pretix.base.models import Item, ItemVariation, Quota, Voucher
from pretix.base.models import Item, Voucher
from pretix.control.forms import SplitDateTimePickerWidget
from pretix.control.forms.widgets import Select2
from pretix.control.forms.widgets import Select2, Select2ItemVarQuota
from pretix.control.signals import voucher_form_validation
class FakeChoiceField(forms.ChoiceField):
def valid_value(self, value):
return True
class VoucherForm(I18nModelForm):
itemvar = forms.ChoiceField(
itemvar = FakeChoiceField(
label=_("Product"),
help_text=_(
"This product is added to the user's cart if the voucher is redeemed."
@@ -72,43 +77,48 @@ class VoucherForm(I18nModelForm):
del self.fields['subevent']
choices = []
for i in self.instance.event.items.prefetch_related('variations').all():
variations = list(i.variations.all())
if variations:
choices.append((str(i.pk), _('{product} Any variation').format(product=i.name)))
for v in variations:
choices.append(('%d-%d' % (i.pk, v.pk), '%s %s' % (i.name, v.value)))
else:
choices.append((str(i.pk), i.name))
for q in self.instance.event.quotas.all():
choices.append(('q-%d' % q.pk, _('Any product in quota "{quota}"').format(quota=q)))
self.fields['itemvar'].choices = choices
self.fields['itemvar'].widget = Select2ItemVarQuota(
attrs={
'data-model-select2': 'generic',
'data-select2-url': reverse('control:event.vouchers.itemselect2', kwargs={
'event': instance.event.slug,
'organizer': instance.event.organizer.slug,
}),
'data-placeholder': ''
}
)
self.fields['itemvar'].widget.choices = self.fields['itemvar'].choices
self.fields['itemvar'].required = True
def clean(self):
data = super().clean()
if not self._errors:
itemid = quotaid = None
iv = self.data.get('itemvar', '')
if iv.startswith('q-'):
quotaid = iv[2:]
elif '-' in iv:
itemid, varid = iv.split('-')
else:
itemid, varid = iv, None
if itemid:
self.instance.item = Item.objects.get(pk=itemid, event=self.instance.event)
if varid:
self.instance.variation = ItemVariation.objects.get(pk=varid, item=self.instance.item)
try:
itemid = quotaid = None
iv = self.data.get('itemvar', '')
if iv.startswith('q-'):
quotaid = iv[2:]
elif '-' in iv:
itemid, varid = iv.split('-')
else:
self.instance.variation = None
self.instance.quota = None
itemid, varid = iv, None
else:
self.instance.quota = Quota.objects.get(pk=quotaid, event=self.instance.event)
self.instance.item = None
self.instance.variation = None
if itemid:
self.instance.item = self.instance.event.items.get(pk=itemid)
if varid:
self.instance.variation = self.instance.item.variations.get(pk=varid)
else:
self.instance.variation = None
self.instance.quota = None
else:
self.instance.quota = self.instance.event.quotas.get(pk=quotaid)
self.instance.item = None
self.instance.variation = None
except ObjectDoesNotExist:
raise ValidationError(_("Invalid product selected."))
if 'codes' in data:
data['codes'] = [a.strip() for a in data.get('codes', '').strip().split("\n") if a]

View File

@@ -36,3 +36,23 @@ class Select2(Select2Mixin, forms.Select):
class Select2Multiple(Select2Mixin, forms.SelectMultiple):
pass
class Select2ItemVarQuotaMixin(Select2Mixin):
def options(self, name, value, attrs=None):
if value and value[0]:
yield self.create_option(
None,
value[0],
value[0],
True,
0,
subindex=None,
attrs=attrs
)
return
class Select2ItemVarQuota(Select2ItemVarQuotaMixin, forms.Select):
pass

View File

@@ -142,6 +142,7 @@ urlpatterns = [
url(r'^vouchers/$', vouchers.VoucherList.as_view(), name='event.vouchers'),
url(r'^vouchers/tags/$', vouchers.VoucherTags.as_view(), name='event.vouchers.tags'),
url(r'^vouchers/rng$', vouchers.VoucherRNG.as_view(), name='event.vouchers.rng'),
url(r'^vouchers/item_select$', typeahead.itemvarquota_select2, name='event.vouchers.itemselect2'),
url(r'^vouchers/(?P<voucher>\d+)/$', vouchers.VoucherUpdate.as_view(), name='event.voucher'),
url(r'^vouchers/(?P<voucher>\d+)/delete$', vouchers.VoucherDelete.as_view(),
name='event.voucher.delete'),

View File

@@ -158,6 +158,85 @@ def checkinlist_select2(request, **kwargs):
return JsonResponse(doc)
@event_permission_required(None)
def itemvarquota_select2(request, **kwargs):
query = request.GET.get('query', '')
try:
page = int(request.GET.get('page', '1'))
except ValueError:
page = 1
choices = []
if not request.event.has_subevents:
# We are very unlikely to need pagination
itemqs = request.event.items.prefetch_related('variations').filter(name__icontains=i18ncomp(query))
quotaqs = request.event.quotas.filter(name__icontains=query)
more = False
else:
# We can't do proper pagination on a UNION-like query, so we hack it.
if query:
# Don't paginate
quotaf = Q(name__icontains=query)
try:
dt = parse(query)
except ValueError:
pass
else:
tz = request.event.timezone
if dt and request.event.has_subevents:
dt_start = make_aware(datetime.combine(dt.date(), time(hour=0, minute=0, second=0)), tz)
dt_end = make_aware(datetime.combine(dt.date(), time(hour=23, minute=59, second=59)), tz)
quotaf |= Q(subevent__date_from__gte=dt_start) & Q(subevent__date_from__lte=dt_end)
itemqs = request.event.items.prefetch_related('variations').filter(name__icontains=i18ncomp(query))
quotaqs = request.event.quotas.filter(quotaf).select_related('subevent')
more = False
else:
if page == 1:
itemqs = request.event.items.prefetch_related('variations').filter(name__icontains=i18ncomp(query))
else:
itemqs = request.event.items.none()
quotaqs = request.event.quotas.filter(name__icontains=query).select_related('subevent')
total = quotaqs.count()
pagesize = 20
offset = (page - 1) * pagesize
quotaqs = quotaqs[offset:offset + pagesize]
more = total >= (offset + pagesize)
for i in itemqs:
variations = list(i.variations.all())
if variations:
choices.append((str(i.pk), _('{product} Any variation').format(product=i.name), ''))
for v in variations:
choices.append(('%d-%d' % (i.pk, v.pk), '%s %s' % (i.name, v.value), ''))
else:
choices.append((str(i.pk), i.name, ''))
for q in quotaqs:
if request.event.has_subevents:
choices.append(('q-%d' % q.pk,
_('Any product in quota "{quota}"').format(
quota=q
), str(q.subevent)))
else:
choices.append(('q-%d' % q.pk, _('Any product in quota "{quota}"').format(quota=q), ''))
doc = {
'results': [
{
'id': k,
'text': str(v),
'event': str(t),
}
for k, v, t in choices
],
'pagination': {
"more": more
}
}
return JsonResponse(doc)
def organizer_select2(request):
term = request.GET.get('query', '')
try:

View File

@@ -281,7 +281,23 @@ var form_handlers = function (el) {
page: params.page || 1
}
}
}
},
templateResult: function (res) {
if (!res.id) {
return res.text;
}
var $ret = $("<span>").append(
$("<span>").addClass("primary").append($("<div>").text(res.text).html())
);
if (res.event) {
$ret.append(
$("<span>").addClass("secondary").append(
$("<span>").addClass("fa fa-calendar fa-fw")
).append(" ").append($("<div>").text(res.event).html())
);
}
return $ret;
},
}).on("select2:select", function () {
// Allow continuing to select
if ($s.hasAttribute("multiple")) {

View File

@@ -322,16 +322,17 @@ body.loading #wrapper {
width: 300px;
}
.event-dropdown, .mobile-event-dropdown, .select2-results {
.event-name-full {
.event-name-full, .primary {
display: block;
}
.event-daterange, .event-organizer {
.event-daterange, .event-organizer, .secondary {
display: block;
font-size: $font-size-small;
color: $text-muted;
}
.active .event-daterange, .active .event-organizer, .active a,
.select2-results__option--highlighted .event-daterange,
.select2-results__option--highlighted .secondary,
.select2-results__option--highlighted .event-organizer {
color: white;
}