First UI steps

This commit is contained in:
Raphael Michel
2016-11-30 15:47:21 +01:00
parent bfd87f11dd
commit 9e9fc36b50
12 changed files with 264 additions and 102 deletions

View File

@@ -174,14 +174,14 @@ class VoucherForm(I18nModelForm):
class VoucherBulkForm(VoucherForm): class VoucherBulkForm(VoucherForm):
codes = forms.CharField( number = forms.IntegerField(
widget=forms.Textarea, label=_("Number"),
label=_("Codes"),
help_text=_(
"Add one voucher code per line. We suggest that you copy this list and save it into a file."
),
required=True required=True
) )
itemvar = forms.ChoiceField(
label=_("Product"),
widget=forms.RadioSelect
)
class Meta: class Meta:
model = Voucher model = Voucher

View File

@@ -29,6 +29,7 @@
<script type="text/javascript" src="{% static "pretixcontrol/js/sb-admin-2.js" %}"></script> <script type="text/javascript" src="{% static "pretixcontrol/js/sb-admin-2.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/main.js" %}"></script> <script type="text/javascript" src="{% static "pretixcontrol/js/ui/main.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/quota.js" %}"></script> <script type="text/javascript" src="{% static "pretixcontrol/js/ui/quota.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/voucher.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/question.js" %}"></script> <script type="text/javascript" src="{% static "pretixcontrol/js/ui/question.js" %}"></script>
{% endcompress %} {% endcompress %}
{{ html_head|safe }} {{ html_head|safe }}

View File

@@ -2,68 +2,144 @@
{% load i18n %} {% load i18n %}
{% load eventsignal %} {% load eventsignal %}
{% load bootstrap3 %} {% load bootstrap3 %}
{% load capture_tags %}
{% block title %}{% trans "Voucher" %}{% endblock %} {% block title %}{% trans "Voucher" %}{% endblock %}
{% block inside %} {% block inside %}
<h1>{% trans "Create multiple voucher" %}</h1> <h1>{% trans "Create new vouchers" %}</h1>
<form action="" method="post" class="form-horizontal"> <form action="" method="post" class="form-inline" id="voucher-create">
{% csrf_token %} {% csrf_token %}
{% bootstrap_form_errors form %} {% bootstrap_form_errors form %}
<fieldset> {% capture as number_field silent %}&nbsp;{% bootstrap_field form.number layout="inline" %}&nbsp;{% endcapture %}
<legend>{% trans "Voucher codes" %}</legend> {% capture as max_usages_field silent %}&nbsp;{% bootstrap_field form.max_usages layout="inline" %}&nbsp;{% endcapture %}
<div class="form-group"> {% capture as valid_until_field silent %}&nbsp;{% bootstrap_field form.valid_until layout="inline" %}&nbsp;{% endcapture %}
<div class="col-md-6 col-md-offset-3"> {% capture as value_field_percent silent %}&nbsp;
<div class="input-group"> <input class="form-control price" name="price[percent]" type="text">&nbsp;{% endcapture %}
<input type="text" class="form-control input-xs" {% capture as value_field_subtract silent %}&nbsp;
id="voucher-bulk-codes-num" <input class="form-control price" name="price[subtract]" type="text">&nbsp;{% endcapture %}
placeholder="{% trans "Number" %}"> {% capture as value_field_set silent %}&nbsp;
<div class="input-group-btn"> <input class="form-control price" name="price[set]" type="text">&nbsp;{% endcapture %}
<button class="btn btn-default" type="button" id="voucher-bulk-codes-generate"
data-rng-url="{% url 'control:event.vouchers.rng' organizer=request.event.organizer.slug event=request.event.slug %}"> {% bootstrap_form_errors form type="all" %}
{% trans "Generate random codes" %}
</button> <div class="wizard-step" id="step-number">
</div> <div class="wizard-step-inner">
</div> <h4>{% trans "How many vouchers do you want to create?" %}</h4>
</div> <div class="form-line">
</div>
{% bootstrap_field form.codes layout="horizontal" %}
{% bootstrap_field form.max_usages layout="horizontal" %}
</fieldset>
<fieldset>
<legend>{% trans "Voucher details" %}</legend>
{% bootstrap_field form.valid_until layout="horizontal" %}
{% bootstrap_field form.block_quota layout="horizontal" %}
{% bootstrap_field form.allow_ignore_quota layout="horizontal" %}
<div class="form-group">
<label class="col-md-3 control-label" for="id_tag">{% trans "Price effect" %}</label>
<div class="col-md-5">
{% bootstrap_field form.price_mode show_label=False form_group_class="" %}
</div>
<div class="col-md-4">
{% bootstrap_field form.value show_label=False form_group_class="" %}
</div>
</div>
{% bootstrap_field form.itemvar layout="horizontal" %}
<div class="form-group">
<div class="col-md-9 col-md-offset-3">
<div class="controls">
<div class="alert alert-info">
{% blocktrans trimmed %} {% blocktrans trimmed %}
If you choose "any product" for a specific quota and choose to reserve quota for this Create {{ number_field }} voucher codes. Each of them can be redeemed {{ max_usages_field }}
voucher above, the product can still be unavailable to the voucher holder if another quota times.
associated with the product is sold out!
{% endblocktrans %} {% endblocktrans %}
</div> </div>
</div> </div>
</div> </div>
<div class="wizard-step" id="step-valid">
<div class="wizard-step-inner">
<h4>{% trans "How long should the vouchers be valid?" %}</h4>
<div class="radio radio-alt">
<label>
<input type="radio" name="has_valid_until" value="no">
{% trans "The whole presale period" %}
</label>
</div> </div>
{% bootstrap_field form.tag layout="horizontal" %} <div class="radio radio-alt">
{% bootstrap_field form.comment layout="horizontal" %} <label>
</fieldset> <input type="radio" name="has_valid_until" value="yes">
{% blocktrans trimmed %}
Only valid until {{ valid_until_field }}
{% endblocktrans %}
</label>
</div>
</div>
</div>
<div class="wizard-step" id="step-products">
<div class="wizard-step-inner">
<h4>{% trans "For which products should the vouchers be applicable?" %}</h4>
{% bootstrap_field form.itemvar layout="inline" %}
</div>
</div>
<div class="wizard-step" id="step-price">
<div class="wizard-step-inner">
<h4>{% trans "Should the vouchers modify the product's price?" %}</h4>
<div class="radio radio-alt">
<label>
<input type="radio" name="price_mode" value="none">
{% trans "No, just allow to buy this product" %}
<span class="help-block">
{% trans "This is useful if you have products that can only be bought using vouchers. It also allows you to just block quota for someone (see next step)." %}
</span>
</label>
</div>
<div class="radio radio-alt">
<label>
<input type="radio" name="price_mode" value="percent">
{% blocktrans trimmed %}
Reduce price by {{ value_field_percent }} %
{% endblocktrans %}
</label>
</div>
<div class="radio radio-alt">
<label>
<input type="radio" name="price_mode" value="subtract">
{% blocktrans trimmed with currency=request.event.currency %}
Reduce price by {{ value_field_subtract }} {{ currency }}
{% endblocktrans %}
</label>
</div>
<div class="radio radio-alt">
<label>
<input type="radio" name="price_mode" value="set">
{% blocktrans trimmed with currency=request.event.currency %}
Change price to {{ value_field_set }} {{ currency }}
{% endblocktrans %}
</label>
</div>
</div>
</div>
<div class="wizard-step" id="step-block">
<div class="wizard-step-inner">
<h4>{% trans "Should the vouchers block quota?" %}</h4>
<div class="radio radio-alt">
<label>
<input type="radio" name="block_quota" value="no">
{% trans "No" %}
</label>
</div>
<div class="radio radio-alt">
<label>
<input type="radio" name="block_quota" value="yes">
{% trans "Yes" %}
<span class="help-block">
{% trans "If you select this option, these vouchers will be guaranteed, i.e. the applicable quotas will be reduced in a way that these vouchers can be redeemed as long as they are valid, even if your event sells out otherwise." %}
</span>
</label>
</div>
</div>
</div>
<div class="wizard-step" id="step-advanced">
<div class="wizard-step-inner">
<a href="#" id="wizard-advanced-show">Show advanced options</a>
<div class="wizard-advanced">
<h4>{% trans "Advanced options" %}</h4>
{% bootstrap_field form.allow_ignore_quota %}
<p><strong>{% trans "Comment" %}</strong></p>
{% bootstrap_field form.comment layout="inline" form_group_class="comment" %}
<div class="help-block">
{% trans "The text entered in this field will not be visible to the user and is available for your convenience." %}
</div>
</div>
</div>
</div>
{% eventsignal request.event "pretix.control.signals.voucher_form_html" form=form %} {% eventsignal request.event "pretix.control.signals.voucher_form_html" form=form %}
</form>
<div class="form-group submit-group"> <div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save"> <button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %} {% trans "Create" %}
</button> </button>
</div> </div>
</form>
{% endblock %} {% endblock %}

View File

@@ -90,7 +90,6 @@ urlpatterns = [
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'),
url(r'^vouchers/add$', vouchers.VoucherCreate.as_view(), name='event.vouchers.add'), 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<code>[0-9A-Z]+)/transition$', orders.OrderTransition.as_view(), url(r'^orders/(?P<code>[0-9A-Z]+)/transition$', orders.OrderTransition.as_view(),
name='event.order.transition'), name='event.order.transition'),
url(r'^orders/(?P<code>[0-9A-Z]+)/resend$', orders.OrderResendLink.as_view(), url(r'^orders/(?P<code>[0-9A-Z]+)/resend$', orders.OrderResendLink.as_view(),

View File

@@ -189,44 +189,6 @@ class VoucherUpdate(EventPermissionRequiredMixin, UpdateView):
class VoucherCreate(EventPermissionRequiredMixin, CreateView): 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 model = Voucher
template_name = 'pretixcontrol/vouchers/bulk.html' template_name = 'pretixcontrol/vouchers/bulk.html'
permission = 'can_change_vouchers' permission = 'can_change_vouchers'
@@ -241,6 +203,11 @@ class VoucherBulkCreate(EventPermissionRequiredMixin, CreateView):
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs['instance'] = Voucher(event=self.request.event) kwargs['instance'] = Voucher(event=self.request.event)
initial = {
}
if 'initial' in kwargs:
initial.update(kwargs['initial'])
kwargs['initial'] = initial
return kwargs return kwargs
@transaction.atomic @transaction.atomic

View File

@@ -193,6 +193,7 @@ INSTALLED_APPS = [
'django_otp.plugins.otp_totp', 'django_otp.plugins.otp_totp',
'django_otp.plugins.otp_static', 'django_otp.plugins.otp_static',
'statici18n', 'statici18n',
'capture_tag',
] ]
try: try:

View File

@@ -17,6 +17,7 @@ python-u2flib-server==4.*
# https://github.com/celery/celery/pull/3199 # https://github.com/celery/celery/pull/3199
git+https://github.com/pretix/celery.git@pretix#egg=celery git+https://github.com/pretix/celery.git@pretix#egg=celery
django-statici18n==1.2.* django-statici18n==1.2.*
django-capture-tag==1.0
# Deployment / static file compilation requirements # Deployment / static file compilation requirements
BeautifulSoup4 BeautifulSoup4

View File

@@ -62,7 +62,8 @@ setup(
'easy-thumbnails>=2.2,<3' 'easy-thumbnails>=2.2,<3'
'PyPDF2', 'BeautifulSoup4', 'html5lib', 'PyPDF2', 'BeautifulSoup4', 'html5lib',
'slimit', 'lxml', 'static3==0.6.1', 'dj-static', 'chardet', '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={ extras_require={
'dev': ['django-debug-toolbar>=1.3.0,<2.0'], 'dev': ['django-debug-toolbar>=1.3.0,<2.0'],

View File

@@ -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;
});
});

View File

@@ -117,3 +117,4 @@ div[data-formset-body], div[data-formset-form], div[data-nested-formset-form], d
.ticketoutput-panel .panel-title { .ticketoutput-panel .panel-title {
line-height: 30px; line-height: 30px;
} }

View File

@@ -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%;
}
}
}

View File

@@ -11,6 +11,7 @@ $fa-font-path: static("fontawesome/fonts");
@import "_flags.scss"; @import "_flags.scss";
@import "_orders.scss"; @import "_orders.scss";
@import "_dashboard.scss"; @import "_dashboard.scss";
@import "_vouchers.scss";
@import "../../pretixbase/scss/webfont.scss"; @import "../../pretixbase/scss/webfont.scss";
footer { footer {