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):
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

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/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/voucher.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/question.js" %}"></script>
{% endcompress %}
{{ html_head|safe }}

View File

@@ -2,68 +2,144 @@
{% load i18n %}
{% load eventsignal %}
{% load bootstrap3 %}
{% load capture_tags %}
{% block title %}{% trans "Voucher" %}{% endblock %}
{% block inside %}
<h1>{% trans "Create multiple voucher" %}</h1>
<form action="" method="post" class="form-horizontal">
<h1>{% trans "Create new vouchers" %}</h1>
<form action="" method="post" class="form-inline" id="voucher-create">
{% csrf_token %}
{% bootstrap_form_errors form %}
<fieldset>
<legend>{% trans "Voucher codes" %}</legend>
<div class="form-group">
<div class="col-md-6 col-md-offset-3">
<div class="input-group">
<input type="text" class="form-control input-xs"
id="voucher-bulk-codes-num"
placeholder="{% trans "Number" %}">
<div class="input-group-btn">
<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 %}">
{% trans "Generate random codes" %}
</button>
</div>
</div>
{% capture as number_field silent %}&nbsp;{% bootstrap_field form.number layout="inline" %}&nbsp;{% endcapture %}
{% capture as max_usages_field silent %}&nbsp;{% bootstrap_field form.max_usages layout="inline" %}&nbsp;{% endcapture %}
{% capture as valid_until_field silent %}&nbsp;{% bootstrap_field form.valid_until layout="inline" %}&nbsp;{% endcapture %}
{% capture as value_field_percent silent %}&nbsp;
<input class="form-control price" name="price[percent]" type="text">&nbsp;{% endcapture %}
{% capture as value_field_subtract silent %}&nbsp;
<input class="form-control price" name="price[subtract]" type="text">&nbsp;{% endcapture %}
{% capture as value_field_set silent %}&nbsp;
<input class="form-control price" name="price[set]" type="text">&nbsp;{% endcapture %}
{% bootstrap_form_errors form type="all" %}
<div class="wizard-step" id="step-number">
<div class="wizard-step-inner">
<h4>{% trans "How many vouchers do you want to create?" %}</h4>
<div class="form-line">
{% blocktrans trimmed %}
Create {{ number_field }} voucher codes. Each of them can be redeemed {{ max_usages_field }}
times.
{% endblocktrans %}
</div>
</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 %}
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 %}
</div>
</div>
</div>
</div>
{% bootstrap_field form.tag layout="horizontal" %}
{% bootstrap_field form.comment layout="horizontal" %}
</fieldset>
{% eventsignal request.event "pretix.control.signals.voucher_form_html" form=form %}
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
</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 class="radio radio-alt">
<label>
<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 %}
</form>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Create" %}
</button>
</div>
{% endblock %}

View File

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

View File

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

View File

@@ -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

View File

@@ -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'],

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 {
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 "_orders.scss";
@import "_dashboard.scss";
@import "_vouchers.scss";
@import "../../pretixbase/scss/webfont.scss";
footer {