diff --git a/src/pretix/base/migrations/0057_auto_20170501_2116.py b/src/pretix/base/migrations/0057_auto_20170501_2116.py new file mode 100644 index 0000000000..b86d50c704 --- /dev/null +++ b/src/pretix/base/migrations/0057_auto_20170501_2116.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-05-01 21:16 +from __future__ import unicode_literals + +import i18nfield.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0056_auto_20170414_1044'), + ] + + operations = [ + migrations.AlterModelOptions( + name='itemaddon', + options={'ordering': ('position', 'pk')}, + ), + migrations.AddField( + model_name='itemaddon', + name='position', + field=models.PositiveIntegerField(default=0, verbose_name='Position'), + ), + migrations.AddField( + model_name='itemvariation', + name='description', + field=i18nfield.fields.I18nTextField(blank=True, help_text='This is shown below the variation name in lists.', null=True, verbose_name='Description'), + ), + ] diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index 0a28b0e088..a72213669f 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -310,6 +310,8 @@ class ItemVariation(models.Model): :type item: Item :param value: A string defining this variation :type value: str + :param description: A short description + :type description: str :param active: Whether this variation is being sold. :type active: bool :param default_price: This variation's default price @@ -327,6 +329,11 @@ class ItemVariation(models.Model): default=True, verbose_name=_("Active"), ) + description = I18nTextField( + verbose_name=_("Description"), + help_text=_("This is shown below the variation name in lists."), + null=True, blank=True, + ) position = models.PositiveIntegerField( default=0, verbose_name=_("Position") @@ -413,9 +420,14 @@ class ItemAddOn(models.Model): default=1, verbose_name=_('Maximum number') ) + position = models.PositiveIntegerField( + default=0, + verbose_name=_("Position") + ) class Meta: unique_together = (('base_item', 'addon_category'),) + ordering = ('position', 'pk') def clean(self): if self.max_count < self.min_count: diff --git a/src/pretix/control/forms/item.py b/src/pretix/control/forms/item.py index 974d31e66a..cbb3d33d46 100644 --- a/src/pretix/control/forms/item.py +++ b/src/pretix/control/forms/item.py @@ -211,6 +211,7 @@ class ItemVariationForm(I18nModelForm): 'value', 'active', 'default_price', + 'description', ] diff --git a/src/pretix/control/templates/pretixcontrol/item/addons.html b/src/pretix/control/templates/pretixcontrol/item/addons.html index 5e4a33debf..3e16a79dbf 100644 --- a/src/pretix/control/templates/pretixcontrol/item/addons.html +++ b/src/pretix/control/templates/pretixcontrol/item/addons.html @@ -24,6 +24,7 @@
{{ form.id }} + {% bootstrap_field form.ORDER form_group_class="" layout="inline" %} {% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
@@ -31,8 +32,12 @@

{% trans "Add-On" %}

-
- + +
@@ -51,6 +56,7 @@
{{ formset.empty_form.id }} + {% bootstrap_field formset.empty_form.ORDER form_group_class="" layout="inline" %} {% bootstrap_field formset.empty_form.DELETE form_group_class="" layout="inline" %}
@@ -59,7 +65,11 @@

{% trans "Add-On" %}

- + +
diff --git a/src/pretix/control/templates/pretixcontrol/item/variations.html b/src/pretix/control/templates/pretixcontrol/item/variations.html index 890a83ee3b..db320ad660 100644 --- a/src/pretix/control/templates/pretixcontrol/item/variations.html +++ b/src/pretix/control/templates/pretixcontrol/item/variations.html @@ -37,6 +37,7 @@ {% bootstrap_form_errors form %} {% bootstrap_field form.active layout='horizontal' %} {% bootstrap_field form.default_price layout='horizontal' %} + {% bootstrap_field form.description layout='horizontal' %}
{% endfor %} @@ -69,6 +70,7 @@
{% bootstrap_field formset.empty_form.active layout='horizontal' %} {% bootstrap_field formset.empty_form.default_price layout='horizontal' %} + {% bootstrap_field formset.empty_form.description layout='horizontal' %}
{% endescapescript %} diff --git a/src/pretix/control/views/item.py b/src/pretix/control/views/item.py index 661fc4920f..f252bf5588 100644 --- a/src/pretix/control/views/item.py +++ b/src/pretix/control/views/item.py @@ -945,7 +945,7 @@ class ItemAddOns(ItemDetailMixin, EventPermissionRequiredMixin, TemplateView): formsetclass = inlineformset_factory( Item, ItemAddOn, form=ItemAddOnForm, formset=ItemAddOnsFormSet, - can_order=False, can_delete=True, extra=0 + can_order=True, can_delete=True, extra=0 ) return formsetclass(self.request.POST if self.request.method == "POST" else None, queryset=ItemAddOn.objects.filter(base_item=self.get_object()), @@ -965,12 +965,13 @@ class ItemAddOns(ItemDetailMixin, EventPermissionRequiredMixin, TemplateView): form.instance.delete() form.instance.pk = None - forms = [ - ef for ef in self.formset.extra_forms + self.formset.initial_forms - if ef not in self.formset.deleted_forms + forms = self.formset.ordered_forms + [ + ef for ef in self.formset.extra_forms + if ef not in self.formset.ordered_forms and ef not in self.formset.deleted_forms ] for i, form in enumerate(forms): form.instance.base_item = self.get_object() + form.instance.position = i created = not form.instance.pk form.save() if form.has_changed(): diff --git a/src/pretix/presale/forms/checkout.py b/src/pretix/presale/forms/checkout.py index 1147402275..b846f451bb 100644 --- a/src/pretix/presale/forms/checkout.py +++ b/src/pretix/presale/forms/checkout.py @@ -3,12 +3,17 @@ from decimal import Decimal from django import forms from django.core.exceptions import ValidationError from django.db.models import Count, Prefetch, Q +from django.forms.widgets import RadioChoiceInput, RadioFieldRenderer +from django.utils.encoding import force_text from django.utils.formats import number_format +from django.utils.html import format_html +from django.utils.safestring import mark_safe from django.utils.timezone import now from django.utils.translation import ugettext_lazy as _ from pretix.base.models import ItemVariation, Question from pretix.base.models.orders import InvoiceAddress +from pretix.base.templatetags.rich_text import rich_text class ContactForm(forms.Form): @@ -146,6 +151,63 @@ class QuestionsForm(forms.Form): self.fields['question_%s' % q.id] = field +# The following will get totally different once Django 1.11 is integrated +class AddOnVariationSelectInput(RadioChoiceInput): + + def __init__(self, name, value, attrs, choice, index): + super().__init__(name, value, attrs, choice, index) + self.description = force_text(choice[2]) + + def render(self, name=None, value=None, attrs=None): + if self.id_for_label: + label_for = format_html(' for="{}"', self.id_for_label) + else: + label_for = '' + attrs = dict(self.attrs, **attrs) if attrs else self.attrs + if self.description: + return format_html( + '{} {} ' + '
{}
', + label_for, self.tag(attrs), self.choice_label, + rich_text(str(self.description)) + ) + else: + return format_html( + '{} {}', + label_for, self.tag(attrs), self.choice_label, + ) + + +class AddOnVariationSelectRenderer(RadioFieldRenderer): + choice_input_class = AddOnVariationSelectInput + + def render(self): + id_ = self.attrs.get('id') + output = [] + for i, choice in enumerate(self.choices): + w = self.choice_input_class(self.name, self.value, self.attrs.copy(), choice, i) + output.append(format_html(self.inner_html, choice_value=force_text(w), sub_widgets='')) + return format_html( + self.outer_html, + id_attr=format_html(' id="{}"', id_) if id_ else '', + content=mark_safe('\n'.join(output)), + ) + + +class AddOnVariationSelect(forms.RadioSelect): + renderer = AddOnVariationSelectRenderer + + +class AddOnVariationField(forms.ChoiceField): + + def valid_value(self, value): + text_value = force_text(value) + for k, v, d in self.choices: + if value == k or text_value == force_text(k): + return True + return False + + class AddOnsForm(forms.Form): """ This form class is responsible for selecting add-ons to a product in the cart. @@ -232,18 +294,18 @@ class AddOnsForm(forms.Form): for i in items: if i.has_variations: - choices = [('', _('no selection'))] + choices = [('', _('no selection'), '')] for v in i.available_variations: cached_availability = v.check_quotas(_cache=quota_cache) - choices.append((v.pk, self._label(event, v, cached_availability))) + choices.append((v.pk, self._label(event, v, cached_availability), v.description)) - field = forms.ChoiceField( + field = AddOnVariationField( choices=choices, label=i.name, required=False, - widget=forms.RadioSelect, - help_text=i.description, - initial=current_addons.get(i.pk) + widget=AddOnVariationSelect, + help_text=rich_text(str(i.description)), + initial=current_addons.get(i.pk), ) else: cached_availability = i.check_quotas(_cache=quota_cache) @@ -251,7 +313,7 @@ class AddOnsForm(forms.Form): label=self._label(event, i, cached_availability), required=False, initial=i.pk in current_addons, - help_text=i.description + help_text=rich_text(str(i.description)), ) self.fields['item_%s' % i.pk] = field diff --git a/src/pretix/presale/templates/pretixpresale/event/index.html b/src/pretix/presale/templates/pretixpresale/event/index.html index 29ecf82c03..ddb2465246 100644 --- a/src/pretix/presale/templates/pretixpresale/event/index.html +++ b/src/pretix/presale/templates/pretixpresale/event/index.html @@ -101,7 +101,10 @@ {{ item.name }} - {% if item.description %}

{{ item.description|localize|rich_text }}

+ {% if item.description %} +
+ {{ item.description|localize|rich_text }} +
{% endif %} {% if item.min_per_order %}

@@ -134,6 +137,11 @@

{{ var }} + {% if var.description %} +
+ {{ var.description|localize|rich_text }} +
+ {% endif %} {% if event.settings.show_quota_left %} {% include "pretixpresale/event/fragment_quota_left.html" with avail=var.cached_availability %} {% endif %} @@ -201,7 +209,10 @@ {% endif %} {{ item.name }} {% if item.description %} -

{{ item.description|localize|rich_text }}

{% endif %} +
+ {{ item.description|localize|rich_text }} +
+ {% endif %} {% if event.settings.show_quota_left %} {% include "pretixpresale/event/fragment_quota_left.html" with avail=item.cached_availability %} {% endif %} diff --git a/src/pretix/presale/templates/pretixpresale/event/voucher.html b/src/pretix/presale/templates/pretixpresale/event/voucher.html index 4a2eaca0e1..87e137793e 100644 --- a/src/pretix/presale/templates/pretixpresale/event/voucher.html +++ b/src/pretix/presale/templates/pretixpresale/event/voucher.html @@ -37,7 +37,10 @@ {% endif %} {{ item.name }} {% if item.description %} -

{{ item.description|localize|rich_text }}

{% endif %} +
+ {{ item.description|localize|rich_text }} +
+ {% endif %}
{% if item.min_price != item.max_price or item.free_price %} @@ -57,6 +60,11 @@
{{ var }} + {% if var.description %} +
+ {{ var.description|localize|rich_text }} +
+ {% endif %}
{% if item.free_price %} @@ -116,7 +124,10 @@ {% endif %} {{ item.name }} {% if item.description %} -

{{ item.description|localize|rich_text }}

{% endif %} +
+ {{ item.description|localize|rich_text }} +
+ {% endif %}
{% if item.free_price %} diff --git a/src/pretix/static/pretixpresale/js/ui/main.js b/src/pretix/static/pretixpresale/js/ui/main.js index 00a3904bb7..13c3db02fa 100644 --- a/src/pretix/static/pretixpresale/js/ui/main.js +++ b/src/pretix/static/pretixpresale/js/ui/main.js @@ -20,6 +20,7 @@ $(function () { $($(this).attr("data-target")).collapse('show'); }); $(".js-only").removeClass("js-only"); + $(".js-hidden").hide(); $(".variations-collapsed").hide(); $("a[data-toggle=variations]").click(function (e) { $(this).parent().parent().parent().find(".variations").slideToggle(); @@ -37,6 +38,12 @@ $(function () { $('[data-toggle="tooltip"]').tooltip(); $("#ajaxerr").on("click", ".ajaxerr-close", ajaxErrDialog.hide); + + // AddOns + $('.addon-variation-description').hide(); + $('.toggle-variation-description').click(function () { + $(this).parent().find('.addon-variation-description').slideToggle(); + }); // Copy answers $(".js-copy-answers").click(function (e) { diff --git a/src/pretix/static/pretixpresale/scss/_event.scss b/src/pretix/static/pretixpresale/scss/_event.scss index b0f3ac2c33..0e3dfd9a9e 100644 --- a/src/pretix/static/pretixpresale/scss/_event.scss +++ b/src/pretix/static/pretixpresale/scss/_event.scss @@ -47,9 +47,18 @@ } } } +.radio .variation-description { + padding-left: 20px; +} +.variation-description { + color: lighten($text-color, 25%); +} .voucher-row { margin-top: 10px; } +.toggle-variation-description { + cursor: pointer; +} #voucher-toggle { display: none; }