diff --git a/src/pretix/control/forms/vouchers.py b/src/pretix/control/forms/vouchers.py index 453773706..b1daded53 100644 --- a/src/pretix/control/forms/vouchers.py +++ b/src/pretix/control/forms/vouchers.py @@ -174,14 +174,14 @@ class VoucherForm(I18nModelForm): class VoucherBulkForm(VoucherForm): - codes = forms.CharField( - widget=forms.Textarea, - label=_("Codes"), - help_text=_( - "Add one voucher code per line. We suggest that you copy this list and save it into a file." - ), + number = forms.IntegerField( + label=_("Number"), required=True ) + itemvar = forms.ChoiceField( + label=_("Product"), + widget=forms.RadioSelect + ) class Meta: model = Voucher diff --git a/src/pretix/control/templates/pretixcontrol/base.html b/src/pretix/control/templates/pretixcontrol/base.html index 4a2d12900..c5d64d216 100644 --- a/src/pretix/control/templates/pretixcontrol/base.html +++ b/src/pretix/control/templates/pretixcontrol/base.html @@ -29,6 +29,7 @@ + {% endcompress %} {{ html_head|safe }} diff --git a/src/pretix/control/templates/pretixcontrol/vouchers/bulk.html b/src/pretix/control/templates/pretixcontrol/vouchers/bulk.html index 522daf4b6..a36f6c8b6 100644 --- a/src/pretix/control/templates/pretixcontrol/vouchers/bulk.html +++ b/src/pretix/control/templates/pretixcontrol/vouchers/bulk.html @@ -2,68 +2,144 @@ {% load i18n %} {% load eventsignal %} {% load bootstrap3 %} +{% load capture_tags %} {% block title %}{% trans "Voucher" %}{% endblock %} {% block inside %} -

{% trans "Create multiple voucher" %}

-
+

{% trans "Create new vouchers" %}

+ {% csrf_token %} {% bootstrap_form_errors form %} -
- {% trans "Voucher codes" %} -
-
-
- -
- -
-
+ {% 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" %} -
-
- {% trans "Voucher details" %} - {% bootstrap_field form.valid_until layout="horizontal" %} - {% bootstrap_field form.block_quota layout="horizontal" %} - {% bootstrap_field form.allow_ignore_quota layout="horizontal" %} -
- -
- {% bootstrap_field form.price_mode show_label=False form_group_class="" %} -
-
- {% bootstrap_field form.value show_label=False form_group_class="" %} -
-
- {% bootstrap_field form.itemvar layout="horizontal" %} -
-
-
-
- {% blocktrans trimmed %} - If you choose "any product" for a specific quota and choose to reserve quota for this - voucher above, the product can still be unavailable to the voucher holder if another quota - associated with the product is sold out! - {% endblocktrans %} -
-
-
-
- {% bootstrap_field form.tag layout="horizontal" %} - {% bootstrap_field form.comment 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 modify the product's price?" %}

+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+

{% 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 {