forked from CGM_Public/pretix_original
Fix #782 -- Select2 widget for item selection for vouchers
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user