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
|
import copy
|
||||||
|
|
||||||
from django import forms
|
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.db.models.functions import Lower
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||||
|
|
||||||
from pretix.base.forms import I18nModelForm
|
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 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
|
from pretix.control.signals import voucher_form_validation
|
||||||
|
|
||||||
|
|
||||||
|
class FakeChoiceField(forms.ChoiceField):
|
||||||
|
def valid_value(self, value):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class VoucherForm(I18nModelForm):
|
class VoucherForm(I18nModelForm):
|
||||||
itemvar = forms.ChoiceField(
|
itemvar = FakeChoiceField(
|
||||||
label=_("Product"),
|
label=_("Product"),
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"This product is added to the user's cart if the voucher is redeemed."
|
"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']
|
del self.fields['subevent']
|
||||||
|
|
||||||
choices = []
|
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'].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):
|
def clean(self):
|
||||||
data = super().clean()
|
data = super().clean()
|
||||||
|
|
||||||
if not self._errors:
|
if not self._errors:
|
||||||
itemid = quotaid = None
|
try:
|
||||||
iv = self.data.get('itemvar', '')
|
itemid = quotaid = None
|
||||||
if iv.startswith('q-'):
|
iv = self.data.get('itemvar', '')
|
||||||
quotaid = iv[2:]
|
if iv.startswith('q-'):
|
||||||
elif '-' in iv:
|
quotaid = iv[2:]
|
||||||
itemid, varid = iv.split('-')
|
elif '-' in iv:
|
||||||
else:
|
itemid, varid = iv.split('-')
|
||||||
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)
|
|
||||||
else:
|
else:
|
||||||
self.instance.variation = None
|
itemid, varid = iv, None
|
||||||
self.instance.quota = None
|
|
||||||
|
|
||||||
else:
|
if itemid:
|
||||||
self.instance.quota = Quota.objects.get(pk=quotaid, event=self.instance.event)
|
self.instance.item = self.instance.event.items.get(pk=itemid)
|
||||||
self.instance.item = None
|
if varid:
|
||||||
self.instance.variation = None
|
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:
|
if 'codes' in data:
|
||||||
data['codes'] = [a.strip() for a in data.get('codes', '').strip().split("\n") if a]
|
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):
|
class Select2Multiple(Select2Mixin, forms.SelectMultiple):
|
||||||
pass
|
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/$', vouchers.VoucherList.as_view(), name='event.vouchers'),
|
||||||
url(r'^vouchers/tags/$', vouchers.VoucherTags.as_view(), name='event.vouchers.tags'),
|
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/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+)/$', vouchers.VoucherUpdate.as_view(), name='event.voucher'),
|
||||||
url(r'^vouchers/(?P<voucher>\d+)/delete$', vouchers.VoucherDelete.as_view(),
|
url(r'^vouchers/(?P<voucher>\d+)/delete$', vouchers.VoucherDelete.as_view(),
|
||||||
name='event.voucher.delete'),
|
name='event.voucher.delete'),
|
||||||
|
|||||||
@@ -158,6 +158,85 @@ def checkinlist_select2(request, **kwargs):
|
|||||||
return JsonResponse(doc)
|
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):
|
def organizer_select2(request):
|
||||||
term = request.GET.get('query', '')
|
term = request.GET.get('query', '')
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -281,7 +281,23 @@ var form_handlers = function (el) {
|
|||||||
page: params.page || 1
|
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 () {
|
}).on("select2:select", function () {
|
||||||
// Allow continuing to select
|
// Allow continuing to select
|
||||||
if ($s.hasAttribute("multiple")) {
|
if ($s.hasAttribute("multiple")) {
|
||||||
|
|||||||
@@ -322,16 +322,17 @@ body.loading #wrapper {
|
|||||||
width: 300px;
|
width: 300px;
|
||||||
}
|
}
|
||||||
.event-dropdown, .mobile-event-dropdown, .select2-results {
|
.event-dropdown, .mobile-event-dropdown, .select2-results {
|
||||||
.event-name-full {
|
.event-name-full, .primary {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.event-daterange, .event-organizer {
|
.event-daterange, .event-organizer, .secondary {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
color: $text-muted;
|
color: $text-muted;
|
||||||
}
|
}
|
||||||
.active .event-daterange, .active .event-organizer, .active a,
|
.active .event-daterange, .active .event-organizer, .active a,
|
||||||
.select2-results__option--highlighted .event-daterange,
|
.select2-results__option--highlighted .event-daterange,
|
||||||
|
.select2-results__option--highlighted .secondary,
|
||||||
.select2-results__option--highlighted .event-organizer {
|
.select2-results__option--highlighted .event-organizer {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user