diff --git a/doc/api/resources/items.rst b/doc/api/resources/items.rst index 91aca9be08..00611141a0 100644 --- a/doc/api/resources/items.rst +++ b/doc/api/resources/items.rst @@ -57,6 +57,8 @@ max_per_order integer This product ca checkin_attention boolean If ``True``, the check-in app should show a warning that this ticket requires special attention if such a product is being scanned. +original_price money (string) An original price, shown for comparison, not used + for price calculations. has_variations boolean Shows whether or not this item has variations. variations list of objects A list with one object for each variation of this item. Can be empty. Only writable during creation, @@ -93,7 +95,7 @@ addons list of objects Definition of a .. versionchanged:: 1.16 - The field ``internal_name`` has been added. + The field ``internal_name`` and ``original_price`` fields have been added. Notes ----- @@ -138,6 +140,7 @@ Endpoints "name": {"en": "Standard ticket"}, "internal_name": "", "default_price": "23.00", + "original_price": null, "category": null, "active": true, "description": null, @@ -221,6 +224,7 @@ Endpoints "name": {"en": "Standard ticket"}, "internal_name": "", "default_price": "23.00", + "original_price": null, "category": null, "active": true, "description": null, @@ -285,6 +289,7 @@ Endpoints "name": {"en": "Standard ticket"}, "internal_name": "", "default_price": "23.00", + "original_price": null, "category": null, "active": true, "description": null, @@ -336,6 +341,7 @@ Endpoints "name": {"en": "Standard ticket"}, "internal_name": "", "default_price": "23.00", + "original_price": null, "category": null, "active": true, "description": null, @@ -419,6 +425,7 @@ Endpoints "name": {"en": "Ticket"}, "internal_name": "", "default_price": "25.00", + "original_price": null, "category": null, "active": true, "description": null, diff --git a/src/pretix/api/serializers/item.py b/src/pretix/api/serializers/item.py index 965fddc1d1..4faae9c5e9 100644 --- a/src/pretix/api/serializers/item.py +++ b/src/pretix/api/serializers/item.py @@ -79,7 +79,7 @@ class ItemSerializer(I18nAwareModelSerializer): 'position', 'picture', 'available_from', 'available_until', 'require_voucher', 'hide_without_voucher', 'allow_cancel', 'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations', - 'variations', 'addons') + 'variations', 'addons', 'original_price') read_only_fields = ('has_variations', 'picture') def get_serializer_context(self): diff --git a/src/pretix/base/migrations/0092_auto_20180511_1224.py b/src/pretix/base/migrations/0092_auto_20180511_1224.py new file mode 100644 index 0000000000..aa28f89773 --- /dev/null +++ b/src/pretix/base/migrations/0092_auto_20180511_1224.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.11 on 2018-05-11 12:24 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('pretixbase', '0091_auto_20180513_1641'), + ] + + operations = [ + migrations.AddField( + model_name='item', + name='original_price', + field=models.DecimalField(blank=True, decimal_places=2, + help_text='If set, this will be displayed next to the current price to show ' + 'that the current price is a discounted one. This is just a cosmetic ' + 'setting and will not actually impact pricing.', + max_digits=7, null=True, verbose_name='Original price'), + ), + ] diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index 9f0a7e22c5..5b1daa8397 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -191,6 +191,8 @@ class Item(LoggedModel): :type min_per_order: int :param checkin_attention: Requires special attention at check-in :type checkin_attention: bool + :param original_price: The item's "original" price. Will not be used for any calculations, will just be shown. + :type original_price: decimal.Decimal """ event = models.ForeignKey( @@ -311,8 +313,15 @@ class Item(LoggedModel): 'attention. You can use this for example for student tickets to indicate to the person at ' 'check-in that the student ID card still needs to be checked.') ) + original_price = models.DecimalField( + verbose_name=_('Original price'), + blank=True, null=True, + max_digits=7, decimal_places=2, + help_text=_('If set, this will be displayed next to the current price to show that the current price is a ' + 'discounted one. This is just a cosmetic setting and will not actually impact pricing.') + ) # !!! Attention: If you add new fields here, also add them to the copying code in - # pretix/control/views/item.py if applicable. + # pretix/control/forms/item.py if applicable. class Meta: verbose_name = _("Product") diff --git a/src/pretix/control/forms/item.py b/src/pretix/control/forms/item.py index 59a2406e47..2c4ebaecb0 100644 --- a/src/pretix/control/forms/item.py +++ b/src/pretix/control/forms/item.py @@ -226,6 +226,7 @@ class ItemCreateForm(I18nModelForm): self.instance.max_per_order = self.cleaned_data['copy_from'].max_per_order self.instance.checkin_attention = self.cleaned_data['copy_from'].checkin_attention self.instance.free_price = self.cleaned_data['copy_from'].free_price + self.instance.original_price = self.cleaned_data['copy_from'].original_price self.instance.position = (self.event.items.aggregate(p=Max('position'))['p'] or 0) + 1 instance = super().save(*args, **kwargs) @@ -325,7 +326,8 @@ class ItemUpdateForm(I18nModelForm): 'allow_cancel', 'max_per_order', 'min_per_order', - 'checkin_attention' + 'checkin_attention', + 'original_price' ] field_classes = { 'available_from': forms.SplitDateTimeField, diff --git a/src/pretix/control/templates/pretixcontrol/item/index.html b/src/pretix/control/templates/pretixcontrol/item/index.html index dd75857e9b..07e89d809f 100644 --- a/src/pretix/control/templates/pretixcontrol/item/index.html +++ b/src/pretix/control/templates/pretixcontrol/item/index.html @@ -38,14 +38,13 @@ {% trans "Check-in" %} {% bootstrap_field form.checkin_attention layout="control" %} - {% if plugin_forms %} -
- {% trans "Additional settings" %} - {% for f in plugin_forms %} - {% bootstrap_form f layout="control" %} - {% endfor %} -
- {% endif %} +
+ {% trans "Additional settings" %} + {% bootstrap_field form.original_price addon_after=request.event.currency layout="control" %} + {% for f in plugin_forms %} + {% bootstrap_form f layout="control" %} + {% endfor %} +
diff --git a/src/pretix/presale/templates/pretixpresale/event/index.html b/src/pretix/presale/templates/pretixpresale/event/index.html index 08f16dda75..792a1d7f31 100644 --- a/src/pretix/presale/templates/pretixpresale/event/index.html +++ b/src/pretix/presale/templates/pretixpresale/event/index.html @@ -288,6 +288,10 @@ {% endif %}
+ {% if item.original_price %} + {{ item.original_price|money:event.currency }} + + {% endif %} {% if item.free_price %}
{{ event.currency }} @@ -307,6 +311,9 @@ {% else %} {{ var.display_price.gross|money:event.currency }} {% endif %} + {% if item.original_price %} + + {% endif %} {% if var.display_price.rate and var.display_price.gross and event.settings.display_net_prices %} {% blocktrans trimmed with rate=var.display_price.rate name=var.display_price.name %} plus {{ rate }}% {{ name }} @@ -383,6 +390,10 @@
+ {% if item.original_price %} + {{ item.original_price|money:event.currency }} + + {% endif %} {% if item.free_price %}
{{ event.currency }} @@ -400,6 +411,9 @@ {% else %} {{ item.display_price.gross|money:event.currency }} {% endif %} + {% if item.original_price %} + + {% endif %} {% if item.display_price.rate and item.display_price.gross and event.settings.display_net_prices %} {% blocktrans trimmed with rate=item.display_price.rate name=item.display_price.name %} plus {{ rate }}% {{ name }} diff --git a/src/pretix/presale/views/widget.py b/src/pretix/presale/views/widget.py index 2b1739ccb3..5c559d9dd9 100644 --- a/src/pretix/presale/views/widget.py +++ b/src/pretix/presale/views/widget.py @@ -177,6 +177,7 @@ class WidgetAPIProductList(View): item.cached_availability[0], item.cached_availability[1] if self.request.event.settings.show_quota_left else None ] if not item.has_variations else None, + 'original_price': item.original_price, 'variations': [ { 'id': var.id, diff --git a/src/pretix/static/pretixpresale/js/widget/widget.js b/src/pretix/static/pretixpresale/js/widget/widget.js index aeba2c71a4..2dff90b8b7 100644 --- a/src/pretix/static/pretixpresale/js/widget/widget.js +++ b/src/pretix/static/pretixpresale/js/widget/widget.js @@ -185,7 +185,10 @@ Vue.component('availbox', { }); Vue.component('pricebox', { template: ('
' - + '{{ priceline }}' + + '{{ priceline }}' + + '' + + '{{ original_line }} ' + + '{{ priceline }}' + '
' + '{{ $root.currency }} ' + '' + '
' - + '' + '' + '
' @@ -293,7 +300,7 @@ Vue.component('item', { + '
' + '' + + ' :field_name="\'price_\' + item.id" :original_price="item.original_price">' + '' + '
{{ pricerange }}
' + '
' diff --git a/src/pretix/static/pretixpresale/scss/_event.scss b/src/pretix/static/pretixpresale/scss/_event.scss index d47c222205..370e1e6003 100644 --- a/src/pretix/static/pretixpresale/scss/_event.scss +++ b/src/pretix/static/pretixpresale/scss/_event.scss @@ -64,6 +64,15 @@ margin: 0; line-height: inherit; } + .price del { + color: $text-muted; + } + .price ins { + color: $brand-success; + font-size: 18px; + font-weight: bold; + text-decoration: none; + } } .radio .variation-description { diff --git a/src/pretix/static/pretixpresale/scss/widget.scss b/src/pretix/static/pretixpresale/scss/widget.scss index 726bf4ad9e..9587595888 100644 --- a/src/pretix/static/pretixpresale/scss/widget.scss +++ b/src/pretix/static/pretixpresale/scss/widget.scss @@ -200,6 +200,16 @@ text-align: right; } + del.pretix-widget-pricebox-original-price { + color: $text-muted; + } + + ins.pretix-widget-pricebox-new-price { + font-size: 120%; + font-weight: bold; + text-decoration: none; + } + .pretix-widget-clear { clear: both; } diff --git a/src/tests/api/test_items.py b/src/tests/api/test_items.py index 646d56659c..86762ddea0 100644 --- a/src/tests/api/test_items.py +++ b/src/tests/api/test_items.py @@ -231,7 +231,8 @@ TEST_ITEM_RES = { "checkin_attention": False, "has_variations": False, "variations": [], - "addons": [] + "addons": [], + "original_price": None }