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 class="form-line">
{% blocktrans trimmed %}
Create {{ number_field }} voucher codes. Each of them can be redeemed {{ max_usages_field }}
times.
{% endblocktrans %}
</div> </div>
</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>
<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> </form>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Create" %}
</button>
</div>
{% 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 {