forked from CGM_Public/pretix_original
Introduce original price (#905)
* Introduce original price * Rebase and styling * Widget
This commit is contained in:
@@ -57,6 +57,8 @@ max_per_order integer This product ca
|
|||||||
checkin_attention boolean If ``True``, the check-in app should show a warning
|
checkin_attention boolean If ``True``, the check-in app should show a warning
|
||||||
that this ticket requires special attention if such
|
that this ticket requires special attention if such
|
||||||
a product is being scanned.
|
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.
|
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.
|
variations list of objects A list with one object for each variation of this item.
|
||||||
Can be empty. Only writable during creation,
|
Can be empty. Only writable during creation,
|
||||||
@@ -93,7 +95,7 @@ addons list of objects Definition of a
|
|||||||
|
|
||||||
.. versionchanged:: 1.16
|
.. versionchanged:: 1.16
|
||||||
|
|
||||||
The field ``internal_name`` has been added.
|
The field ``internal_name`` and ``original_price`` fields have been added.
|
||||||
|
|
||||||
Notes
|
Notes
|
||||||
-----
|
-----
|
||||||
@@ -138,6 +140,7 @@ Endpoints
|
|||||||
"name": {"en": "Standard ticket"},
|
"name": {"en": "Standard ticket"},
|
||||||
"internal_name": "",
|
"internal_name": "",
|
||||||
"default_price": "23.00",
|
"default_price": "23.00",
|
||||||
|
"original_price": null,
|
||||||
"category": null,
|
"category": null,
|
||||||
"active": true,
|
"active": true,
|
||||||
"description": null,
|
"description": null,
|
||||||
@@ -221,6 +224,7 @@ Endpoints
|
|||||||
"name": {"en": "Standard ticket"},
|
"name": {"en": "Standard ticket"},
|
||||||
"internal_name": "",
|
"internal_name": "",
|
||||||
"default_price": "23.00",
|
"default_price": "23.00",
|
||||||
|
"original_price": null,
|
||||||
"category": null,
|
"category": null,
|
||||||
"active": true,
|
"active": true,
|
||||||
"description": null,
|
"description": null,
|
||||||
@@ -285,6 +289,7 @@ Endpoints
|
|||||||
"name": {"en": "Standard ticket"},
|
"name": {"en": "Standard ticket"},
|
||||||
"internal_name": "",
|
"internal_name": "",
|
||||||
"default_price": "23.00",
|
"default_price": "23.00",
|
||||||
|
"original_price": null,
|
||||||
"category": null,
|
"category": null,
|
||||||
"active": true,
|
"active": true,
|
||||||
"description": null,
|
"description": null,
|
||||||
@@ -336,6 +341,7 @@ Endpoints
|
|||||||
"name": {"en": "Standard ticket"},
|
"name": {"en": "Standard ticket"},
|
||||||
"internal_name": "",
|
"internal_name": "",
|
||||||
"default_price": "23.00",
|
"default_price": "23.00",
|
||||||
|
"original_price": null,
|
||||||
"category": null,
|
"category": null,
|
||||||
"active": true,
|
"active": true,
|
||||||
"description": null,
|
"description": null,
|
||||||
@@ -419,6 +425,7 @@ Endpoints
|
|||||||
"name": {"en": "Ticket"},
|
"name": {"en": "Ticket"},
|
||||||
"internal_name": "",
|
"internal_name": "",
|
||||||
"default_price": "25.00",
|
"default_price": "25.00",
|
||||||
|
"original_price": null,
|
||||||
"category": null,
|
"category": null,
|
||||||
"active": true,
|
"active": true,
|
||||||
"description": null,
|
"description": null,
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class ItemSerializer(I18nAwareModelSerializer):
|
|||||||
'position', 'picture', 'available_from', 'available_until',
|
'position', 'picture', 'available_from', 'available_until',
|
||||||
'require_voucher', 'hide_without_voucher', 'allow_cancel',
|
'require_voucher', 'hide_without_voucher', 'allow_cancel',
|
||||||
'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations',
|
'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations',
|
||||||
'variations', 'addons')
|
'variations', 'addons', 'original_price')
|
||||||
read_only_fields = ('has_variations', 'picture')
|
read_only_fields = ('has_variations', 'picture')
|
||||||
|
|
||||||
def get_serializer_context(self):
|
def get_serializer_context(self):
|
||||||
|
|||||||
23
src/pretix/base/migrations/0092_auto_20180511_1224.py
Normal file
23
src/pretix/base/migrations/0092_auto_20180511_1224.py
Normal file
@@ -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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -191,6 +191,8 @@ class Item(LoggedModel):
|
|||||||
:type min_per_order: int
|
:type min_per_order: int
|
||||||
:param checkin_attention: Requires special attention at check-in
|
:param checkin_attention: Requires special attention at check-in
|
||||||
:type checkin_attention: bool
|
: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(
|
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 '
|
'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.')
|
'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
|
# !!! 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:
|
class Meta:
|
||||||
verbose_name = _("Product")
|
verbose_name = _("Product")
|
||||||
|
|||||||
@@ -226,6 +226,7 @@ class ItemCreateForm(I18nModelForm):
|
|||||||
self.instance.max_per_order = self.cleaned_data['copy_from'].max_per_order
|
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.checkin_attention = self.cleaned_data['copy_from'].checkin_attention
|
||||||
self.instance.free_price = self.cleaned_data['copy_from'].free_price
|
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
|
self.instance.position = (self.event.items.aggregate(p=Max('position'))['p'] or 0) + 1
|
||||||
instance = super().save(*args, **kwargs)
|
instance = super().save(*args, **kwargs)
|
||||||
@@ -325,7 +326,8 @@ class ItemUpdateForm(I18nModelForm):
|
|||||||
'allow_cancel',
|
'allow_cancel',
|
||||||
'max_per_order',
|
'max_per_order',
|
||||||
'min_per_order',
|
'min_per_order',
|
||||||
'checkin_attention'
|
'checkin_attention',
|
||||||
|
'original_price'
|
||||||
]
|
]
|
||||||
field_classes = {
|
field_classes = {
|
||||||
'available_from': forms.SplitDateTimeField,
|
'available_from': forms.SplitDateTimeField,
|
||||||
|
|||||||
@@ -38,14 +38,13 @@
|
|||||||
<legend>{% trans "Check-in" %}</legend>
|
<legend>{% trans "Check-in" %}</legend>
|
||||||
{% bootstrap_field form.checkin_attention layout="control" %}
|
{% bootstrap_field form.checkin_attention layout="control" %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{% if plugin_forms %}
|
<fieldset>
|
||||||
<fieldset>
|
<legend>{% trans "Additional settings" %}</legend>
|
||||||
<legend>{% trans "Additional settings" %}</legend>
|
{% bootstrap_field form.original_price addon_after=request.event.currency layout="control" %}
|
||||||
{% for f in plugin_forms %}
|
{% for f in plugin_forms %}
|
||||||
{% bootstrap_form f layout="control" %}
|
{% bootstrap_form f layout="control" %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-lg-2">
|
<div class="col-xs-12 col-lg-2">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
|
|||||||
@@ -288,6 +288,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2 col-xs-6 price">
|
<div class="col-md-2 col-xs-6 price">
|
||||||
|
{% if item.original_price %}
|
||||||
|
<del>{{ item.original_price|money:event.currency }}</del>
|
||||||
|
<ins>
|
||||||
|
{% endif %}
|
||||||
{% if item.free_price %}
|
{% if item.free_price %}
|
||||||
<div class="input-group input-group-price">
|
<div class="input-group input-group-price">
|
||||||
<span class="input-group-addon">{{ event.currency }}</span>
|
<span class="input-group-addon">{{ event.currency }}</span>
|
||||||
@@ -307,6 +311,9 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{{ var.display_price.gross|money:event.currency }}
|
{{ var.display_price.gross|money:event.currency }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if item.original_price %}
|
||||||
|
</ins>
|
||||||
|
{% endif %}
|
||||||
{% if var.display_price.rate and var.display_price.gross and event.settings.display_net_prices %}
|
{% if var.display_price.rate and var.display_price.gross and event.settings.display_net_prices %}
|
||||||
<small>{% blocktrans trimmed with rate=var.display_price.rate name=var.display_price.name %}
|
<small>{% blocktrans trimmed with rate=var.display_price.rate name=var.display_price.name %}
|
||||||
<strong>plus</strong> {{ rate }}% {{ name }}
|
<strong>plus</strong> {{ rate }}% {{ name }}
|
||||||
@@ -383,6 +390,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2 col-xs-6 price">
|
<div class="col-md-2 col-xs-6 price">
|
||||||
|
{% if item.original_price %}
|
||||||
|
<del>{{ item.original_price|money:event.currency }}</del>
|
||||||
|
<ins>
|
||||||
|
{% endif %}
|
||||||
{% if item.free_price %}
|
{% if item.free_price %}
|
||||||
<div class="input-group input-group-price">
|
<div class="input-group input-group-price">
|
||||||
<span class="input-group-addon">{{ event.currency }}</span>
|
<span class="input-group-addon">{{ event.currency }}</span>
|
||||||
@@ -400,6 +411,9 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{{ item.display_price.gross|money:event.currency }}
|
{{ item.display_price.gross|money:event.currency }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if item.original_price %}
|
||||||
|
</ins>
|
||||||
|
{% endif %}
|
||||||
{% if item.display_price.rate and item.display_price.gross and event.settings.display_net_prices %}
|
{% if item.display_price.rate and item.display_price.gross and event.settings.display_net_prices %}
|
||||||
<small>{% blocktrans trimmed with rate=item.display_price.rate name=item.display_price.name %}
|
<small>{% blocktrans trimmed with rate=item.display_price.rate name=item.display_price.name %}
|
||||||
<strong>plus</strong> {{ rate }}% {{ name }}
|
<strong>plus</strong> {{ rate }}% {{ name }}
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ class WidgetAPIProductList(View):
|
|||||||
item.cached_availability[0],
|
item.cached_availability[0],
|
||||||
item.cached_availability[1] if self.request.event.settings.show_quota_left else None
|
item.cached_availability[1] if self.request.event.settings.show_quota_left else None
|
||||||
] if not item.has_variations else None,
|
] if not item.has_variations else None,
|
||||||
|
'original_price': item.original_price,
|
||||||
'variations': [
|
'variations': [
|
||||||
{
|
{
|
||||||
'id': var.id,
|
'id': var.id,
|
||||||
|
|||||||
@@ -185,7 +185,10 @@ Vue.component('availbox', {
|
|||||||
});
|
});
|
||||||
Vue.component('pricebox', {
|
Vue.component('pricebox', {
|
||||||
template: ('<div class="pretix-widget-pricebox">'
|
template: ('<div class="pretix-widget-pricebox">'
|
||||||
+ '<span v-if="!free_price">{{ priceline }}</span>'
|
+ '<span v-if="!free_price && !original_price">{{ priceline }}</span>'
|
||||||
|
+ '<span v-if="!free_price && original_price">'
|
||||||
|
+ '<del class="pretix-widget-pricebox-original-price">{{ original_line }}</del> '
|
||||||
|
+ '<ins class="pretix-widget-pricebox-new-price">{{ priceline }}</ins></span>'
|
||||||
+ '<div v-if="free_price">'
|
+ '<div v-if="free_price">'
|
||||||
+ '{{ $root.currency }} '
|
+ '{{ $root.currency }} '
|
||||||
+ '<input type="number" class="pretix-widget-pricebox-price-input" placeholder="0" '
|
+ '<input type="number" class="pretix-widget-pricebox-price-input" placeholder="0" '
|
||||||
@@ -199,7 +202,8 @@ Vue.component('pricebox', {
|
|||||||
props: {
|
props: {
|
||||||
price: Object,
|
price: Object,
|
||||||
free_price: Boolean,
|
free_price: Boolean,
|
||||||
field_name: String
|
field_name: String,
|
||||||
|
original_price: String
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
display_price: function () {
|
display_price: function () {
|
||||||
@@ -209,6 +213,9 @@ Vue.component('pricebox', {
|
|||||||
return roundTo(parseFloat(this.price.gross), 2).toFixed(2);
|
return roundTo(parseFloat(this.price.gross), 2).toFixed(2);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
original_line: function () {
|
||||||
|
return this.$root.currency + " " + roundTo(parseFloat(this.original_price), 2).toFixed(2);
|
||||||
|
},
|
||||||
priceline: function () {
|
priceline: function () {
|
||||||
if (this.price.gross === "0.00") {
|
if (this.price.gross === "0.00") {
|
||||||
return strings.free;
|
return strings.free;
|
||||||
@@ -247,7 +254,7 @@ Vue.component('variation', {
|
|||||||
+ '</div>'
|
+ '</div>'
|
||||||
|
|
||||||
+ '<div class="pretix-widget-item-price-col">'
|
+ '<div class="pretix-widget-item-price-col">'
|
||||||
+ '<pricebox :price="variation.price" :free_price="item.free_price"'
|
+ '<pricebox :price="variation.price" :free_price="item.free_price" :original_price="item.original_price"'
|
||||||
+ ' :field_name="\'price_\' + item.id + \'_\' + variation.id">'
|
+ ' :field_name="\'price_\' + item.id + \'_\' + variation.id">'
|
||||||
+ '</pricebox>'
|
+ '</pricebox>'
|
||||||
+ '</div>'
|
+ '</div>'
|
||||||
@@ -293,7 +300,7 @@ Vue.component('item', {
|
|||||||
|
|
||||||
+ '<div class="pretix-widget-item-price-col">'
|
+ '<div class="pretix-widget-item-price-col">'
|
||||||
+ '<pricebox :price="item.price" :free_price="item.free_price" v-if="!item.has_variations"'
|
+ '<pricebox :price="item.price" :free_price="item.free_price" v-if="!item.has_variations"'
|
||||||
+ ' :field_name="\'price_\' + item.id">'
|
+ ' :field_name="\'price_\' + item.id" :original_price="item.original_price">'
|
||||||
+ '</pricebox>'
|
+ '</pricebox>'
|
||||||
+ '<div class="pretix-widget-pricebox" v-if="item.has_variations">{{ pricerange }}</div>'
|
+ '<div class="pretix-widget-pricebox" v-if="item.has_variations">{{ pricerange }}</div>'
|
||||||
+ '</div>'
|
+ '</div>'
|
||||||
|
|||||||
@@ -64,6 +64,15 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: inherit;
|
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 {
|
.radio .variation-description {
|
||||||
|
|||||||
@@ -200,6 +200,16 @@
|
|||||||
text-align: right;
|
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 {
|
.pretix-widget-clear {
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -231,7 +231,8 @@ TEST_ITEM_RES = {
|
|||||||
"checkin_attention": False,
|
"checkin_attention": False,
|
||||||
"has_variations": False,
|
"has_variations": False,
|
||||||
"variations": [],
|
"variations": [],
|
||||||
"addons": []
|
"addons": [],
|
||||||
|
"original_price": None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user