-
+ {% capture as number_field silent %} {% bootstrap_field form.number layout="inline" %} {% endcapture %}
+ {% capture as max_usages_field silent %} {% bootstrap_field form.max_usages layout="inline" %} {% endcapture %}
+ {% capture as valid_until_field silent %} {% bootstrap_field form.valid_until layout="inline" %} {% endcapture %}
+ {% capture as value_field_percent silent %}
+
{% endcapture %}
+ {% capture as value_field_subtract silent %}
+
{% endcapture %}
+ {% capture as value_field_set silent %}
+
{% endcapture %}
+
+ {% bootstrap_form_errors form type="all" %}
+
+
+
+
{% trans "How many vouchers do you want to create?" %}
+
+ {% blocktrans trimmed %}
+ Create {{ number_field }} voucher codes. Each of them can be redeemed {{ max_usages_field }}
+ times.
+ {% endblocktrans %}
- {% bootstrap_field form.codes layout="horizontal" %}
- {% bootstrap_field form.max_usages layout="horizontal" %}
-
-
- {% eventsignal request.event "pretix.control.signals.voucher_form_html" form=form %}
-
-
+
+
+
+
{% trans "How long should the vouchers be valid?" %}
+
+
+
+
+
+
+
+
+
+
+
+
{% trans "For which products should the vouchers be applicable?" %}
+ {% bootstrap_field form.itemvar layout="inline" %}
+
+
+
+
+
+
+
+
{% trans "Should the vouchers block quota?" %}
+
+
+
+
+
+
+
+
+
+
+
+
Show advanced options
+
+
{% trans "Advanced options" %}
+ {% bootstrap_field form.allow_ignore_quota %}
+
{% trans "Comment" %}
+ {% bootstrap_field form.comment layout="inline" form_group_class="comment" %}
+
+ {% trans "The text entered in this field will not be visible to the user and is available for your convenience." %}
+
+
+
+
+
+ {% eventsignal request.event "pretix.control.signals.voucher_form_html" form=form %}
+
+
+
{% endblock %}
diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py
index fff0f5053..e69941b3b 100644
--- a/src/pretix/control/urls.py
+++ b/src/pretix/control/urls.py
@@ -90,7 +90,6 @@ urlpatterns = [
url(r'^vouchers/(?P
\d+)/delete$', vouchers.VoucherDelete.as_view(),
name='event.voucher.delete'),
url(r'^vouchers/add$', vouchers.VoucherCreate.as_view(), name='event.vouchers.add'),
- url(r'^vouchers/bulk_add$', vouchers.VoucherBulkCreate.as_view(), name='event.vouchers.bulk'),
url(r'^orders/(?P[0-9A-Z]+)/transition$', orders.OrderTransition.as_view(),
name='event.order.transition'),
url(r'^orders/(?P[0-9A-Z]+)/resend$', orders.OrderResendLink.as_view(),
diff --git a/src/pretix/control/views/vouchers.py b/src/pretix/control/views/vouchers.py
index 40fb96b0f..ff852ebf2 100644
--- a/src/pretix/control/views/vouchers.py
+++ b/src/pretix/control/views/vouchers.py
@@ -189,44 +189,6 @@ class VoucherUpdate(EventPermissionRequiredMixin, UpdateView):
class VoucherCreate(EventPermissionRequiredMixin, CreateView):
- model = Voucher
- template_name = 'pretixcontrol/vouchers/detail.html'
- permission = 'can_change_vouchers'
- context_object_name = 'voucher'
-
- def get_form_class(self):
- form_class = VoucherForm
- for receiver, response in voucher_form_class.send(self.request.event, cls=form_class):
- if response:
- form_class = response
- return form_class
-
- def get_success_url(self) -> str:
- return reverse('control:event.vouchers', kwargs={
- 'organizer': self.request.event.organizer.slug,
- 'event': self.request.event.slug,
- })
-
- def get_form_kwargs(self):
- kwargs = super().get_form_kwargs()
- kwargs['instance'] = Voucher(event=self.request.event)
- return kwargs
-
- @transaction.atomic
- def form_valid(self, form):
- form.instance.event = self.request.event
- messages.success(self.request, _('The new voucher has been created.'))
- ret = super().form_valid(form)
- form.instance.log_action('pretix.voucher.added', data=dict(form.cleaned_data), user=self.request.user)
- return ret
-
- def post(self, request, *args, **kwargs):
- # TODO: Transform this into an asynchronous call?
- with request.event.lock():
- return super().post(request, *args, **kwargs)
-
-
-class VoucherBulkCreate(EventPermissionRequiredMixin, CreateView):
model = Voucher
template_name = 'pretixcontrol/vouchers/bulk.html'
permission = 'can_change_vouchers'
@@ -241,6 +203,11 @@ class VoucherBulkCreate(EventPermissionRequiredMixin, CreateView):
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['instance'] = Voucher(event=self.request.event)
+ initial = {
+ }
+ if 'initial' in kwargs:
+ initial.update(kwargs['initial'])
+ kwargs['initial'] = initial
return kwargs
@transaction.atomic
diff --git a/src/pretix/settings.py b/src/pretix/settings.py
index c16cad09f..3d8ec3cca 100644
--- a/src/pretix/settings.py
+++ b/src/pretix/settings.py
@@ -193,6 +193,7 @@ INSTALLED_APPS = [
'django_otp.plugins.otp_totp',
'django_otp.plugins.otp_static',
'statici18n',
+ 'capture_tag',
]
try:
diff --git a/src/requirements/production.txt b/src/requirements/production.txt
index 9b04cee22..f2a7882b5 100644
--- a/src/requirements/production.txt
+++ b/src/requirements/production.txt
@@ -17,6 +17,7 @@ python-u2flib-server==4.*
# https://github.com/celery/celery/pull/3199
git+https://github.com/pretix/celery.git@pretix#egg=celery
django-statici18n==1.2.*
+django-capture-tag==1.0
# Deployment / static file compilation requirements
BeautifulSoup4
diff --git a/src/setup.py b/src/setup.py
index 4f15c835a..532c62698 100644
--- a/src/setup.py
+++ b/src/setup.py
@@ -62,7 +62,8 @@ setup(
'easy-thumbnails>=2.2,<3'
'PyPDF2', 'BeautifulSoup4', 'html5lib',
'slimit', 'lxml', 'static3==0.6.1', 'dj-static', 'chardet',
- 'csscompressor', 'mt-940', 'django-markup', 'markdown'
+ 'csscompressor', 'mt-940', 'django-markup', 'markdown',
+ 'django-capture-tag'
],
extras_require={
'dev': ['django-debug-toolbar>=1.3.0,<2.0'],
diff --git a/src/static/pretixcontrol/js/ui/voucher.js b/src/static/pretixcontrol/js/ui/voucher.js
new file mode 100644
index 000000000..bf70abce4
--- /dev/null
+++ b/src/static/pretixcontrol/js/ui/voucher.js
@@ -0,0 +1,79 @@
+/*globals $, Morris, gettext*/
+$(function () {
+ if (!$("#voucher-create").length) {
+ return;
+ }
+
+ function show_step(state_el) {
+ state_el.animate({
+ 'height': 'show',
+ 'opacity': 'show',
+ 'padding-top': 'show',
+ 'padding-bottom': 'show',
+ 'margin-top': 'show',
+ 'margin-bottom': 'show'
+ }, 400);
+ var offset = state_el.offset();
+ var body = $("html, body");
+ if (offset.top > $("body").scrollTop() + $(window).height() - 160) {
+ body.animate({scrollTop: offset.top + 200}, '400', 'swing');
+ }
+ }
+
+ $(".wizard-step, .wizard-advanced").hide();
+ $(".wizard-step").first().show();
+
+ $("#id_number, #id_max_usages").on("change keydown keyup", function () {
+ if ($("#id_number").val() && $("#id_max_usages").val()) {
+ show_step($("#step-valid"));
+ }
+ });
+
+ $("#id_valid_until").on("focus change", function () {
+ $("input[name=has_valid_until][value=no]").prop("checked", false);
+ $("input[name=has_valid_until][value=yes]").prop("checked", true);
+ }).on("change dp.change", function () {
+ if ($("input[name=has_valid_until][value=no]").prop("checked") || $("#id_valid_until").val()) {
+ show_step($("#step-products"));
+ }
+ });
+
+ $("input[name=has_valid_until]").on("change", function () {
+ if ($("input[name=has_valid_until][value=no]").prop("checked") || $("#id_valid_until").val()) {
+ show_step($("#step-products"));
+ }
+ });
+
+ $("input[name=itemvar]").on("change", function () {
+ show_step($("#step-price"));
+ });
+
+ $("#step-price input").on("change keydown keyup", function () {
+ var mode = $("input[name=price_mode]:checked").val();
+ var show_next = (
+ mode === 'none'
+ || (mode === 'percent' && $("input[name='price[percent]']").val())
+ || (mode === 'subtract' && $("input[name='price[subtract]']").val())
+ || (mode === 'set' && $("input[name='price[set]']").val())
+ );
+ if (show_next) {
+ show_step($("#step-block"));
+ }
+ });
+ $("#step-price input[type=text]").on("focus change keyup keydown", function () {
+ $("#step-price input[type=radio]").prop("checked", false);
+ $(this).closest(".radio").find("input[type=radio]").prop("checked", true);
+ });
+
+ $("input[name=block_quota]").on("change", function () {
+ show_step($("#step-advanced"));
+ });
+
+ $("#wizard-advanced-show").on("click", function (e) {
+ show_step($(".wizard-advanced"));
+ $(this).animate({'opacity': '0'}, 400);
+ e.preventDefault();
+ return true;
+ });
+
+});
diff --git a/src/static/pretixcontrol/scss/_forms.scss b/src/static/pretixcontrol/scss/_forms.scss
index a73667dd4..c4f24a3b1 100644
--- a/src/static/pretixcontrol/scss/_forms.scss
+++ b/src/static/pretixcontrol/scss/_forms.scss
@@ -117,3 +117,4 @@ div[data-formset-body], div[data-formset-form], div[data-nested-formset-form], d
.ticketoutput-panel .panel-title {
line-height: 30px;
}
+
diff --git a/src/static/pretixcontrol/scss/_vouchers.scss b/src/static/pretixcontrol/scss/_vouchers.scss
new file mode 100644
index 000000000..b4b7c9ce5
--- /dev/null
+++ b/src/static/pretixcontrol/scss/_vouchers.scss
@@ -0,0 +1,35 @@
+#voucher-create {
+ .wizard-step {
+ }
+ .wizard-step-inner {
+ padding: 20px 0;
+ }
+ #id_number, #id_max_usages {
+ width: 75px;
+ }
+ .price {
+ width: 100px;
+ }
+ .form-options {
+ list-style: none;
+ padding: 10px;
+ }
+ .radio {
+ display: block;
+ line-height: 24px;
+ }
+ .radio-alt {
+ line-height: 40px;
+ }
+ .radio .help-block {
+ margin: 0;
+ padding-left: 17px;
+ line-height: 1.5;
+ }
+ .comment {
+ display: block;
+ textarea {
+ width: 100%;
+ }
+ }
+}
diff --git a/src/static/pretixcontrol/scss/main.scss b/src/static/pretixcontrol/scss/main.scss
index 994093f44..841419f1f 100644
--- a/src/static/pretixcontrol/scss/main.scss
+++ b/src/static/pretixcontrol/scss/main.scss
@@ -11,6 +11,7 @@ $fa-font-path: static("fontawesome/fonts");
@import "_flags.scss";
@import "_orders.scss";
@import "_dashboard.scss";
+@import "_vouchers.scss";
@import "../../pretixbase/scss/webfont.scss";
footer {