Refs #99 -- Improve support for currencies with less than 2 decimal places (#783)

* Refs #99 -- Fix stripe support for zero-decimal currencies

* Add new money formatting method

* Force decimal places in many places

* Locale-aware currency rendering

* Fix currencies in more places

* More currency fixes
This commit is contained in:
Raphael Michel
2018-02-26 10:46:07 +01:00
committed by GitHub
parent 29e22a0c6c
commit 3c3e59e932
49 changed files with 467 additions and 211 deletions

View File

@@ -1,5 +1,12 @@
from decimal import ROUND_HALF_UP, Decimal
from django.conf import settings
def round_decimal(dec):
def round_decimal(dec, currency=None):
if currency:
places = settings.CURRENCY_PLACES.get(currency, 2)
return Decimal(dec).quantize(
Decimal('1') / 10 ** places, ROUND_HALF_UP
)
return Decimal(dec).quantize(Decimal('0.01'), ROUND_HALF_UP)

View File

@@ -11,16 +11,16 @@ from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
from .validators import PlaceholderValidator # NOQA
logger = logging.getLogger('pretix.plugins.ticketoutputpdf')
logger = logging.getLogger(__name__)
class BaseI18nModelForm(i18nfield.forms.BaseI18nModelForm):
# compatibility shim for django-i18nfield library
def __init__(self, *args, **kwargs):
event = kwargs.pop('event', None)
if event:
kwargs['locales'] = event.settings.get('locales')
self.event = kwargs.pop('event', None)
if self.event:
kwargs['locales'] = self.event.settings.get('locales')
super().__init__(*args, **kwargs)
@@ -32,9 +32,9 @@ class I18nFormSet(i18nfield.forms.I18nModelFormSet):
# compatibility shim for django-i18nfield library
def __init__(self, *args, **kwargs):
event = kwargs.pop('event', None)
if event:
kwargs['locales'] = event.settings.get('locales')
self.event = kwargs.pop('event', None)
if self.event:
kwargs['locales'] = self.event.settings.get('locales')
super().__init__(*args, **kwargs)

View File

@@ -12,6 +12,8 @@ from i18nfield.forms import I18nFormField # noqa
from i18nfield.strings import LazyI18nString # noqa
from i18nfield.utils import I18nJSONEncoder # noqa
from pretix.base.templatetags.money import money_filter
class LazyDate:
def __init__(self, value):
@@ -24,6 +26,18 @@ class LazyDate:
return date_format(self.value, "SHORT_DATE_FORMAT")
class LazyCurrencyNumber:
def __init__(self, value, currency):
self.value = value
self.currency = currency
def __format__(self, format_spec):
return self.__str__()
def __str__(self):
return money_filter(self.value, self.currency)
class LazyNumber:
def __init__(self, value, decimal_pos=2):
self.value = value

View File

@@ -24,6 +24,7 @@ from reportlab.platypus import (
from pretix.base.decimal import round_decimal
from pretix.base.models import Event, Invoice
from pretix.base.signals import register_invoice_renderers
from pretix.base.templatetags.money import money_filter
class BaseInvoiceRenderer:
@@ -376,14 +377,14 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
Paragraph(line.description, self.stylesheet['Normal']),
"1",
localize(line.tax_rate) + " %",
localize(line.net_value) + " " + self.invoice.event.currency,
localize(line.gross_value) + " " + self.invoice.event.currency,
money_filter(line.net_value, self.invoice.event.currency),
money_filter(line.gross_value, self.invoice.event.currency),
))
else:
tdata.append((
Paragraph(line.description, self.stylesheet['Normal']),
"1",
localize(line.gross_value) + " " + self.invoice.event.currency,
money_filter(line.gross_value, self.invoice.event.currency),
))
taxvalue_map[line.tax_rate, line.tax_name] += line.tax_value
grossvalue_map[line.tax_rate, line.tax_name] += line.gross_value
@@ -391,12 +392,12 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
if has_taxes:
tdata.append([
pgettext('invoice', 'Invoice total'), '', '', '', localize(total) + " " + self.invoice.event.currency
pgettext('invoice', 'Invoice total'), '', '', '', money_filter(total, self.invoice.event.currency)
])
colwidths = [a * doc.width for a in (.50, .05, .15, .15, .15)]
else:
tdata.append([
pgettext('invoice', 'Invoice total'), '', localize(total) + " " + self.invoice.event.currency
pgettext('invoice', 'Invoice total'), '', money_filter(total, self.invoice.event.currency)
])
colwidths = [a * doc.width for a in (.65, .05, .30)]
@@ -436,9 +437,9 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
tax = taxvalue_map[idx]
tdata.append([
localize(rate) + " % " + name,
localize(gross - tax) + " " + self.invoice.event.currency,
localize(gross) + " " + self.invoice.event.currency,
localize(tax) + " " + self.invoice.event.currency,
money_filter(gross - tax, self.invoice.event.currency),
money_filter(gross, self.invoice.event.currency),
money_filter(tax, self.invoice.event.currency),
''
])

View File

@@ -83,6 +83,7 @@ class Invoice(models.Model):
foreign_currency_display = models.CharField(max_length=50, null=True, blank=True)
foreign_currency_rate = models.DecimalField(decimal_places=4, max_digits=10, null=True, blank=True)
foreign_currency_rate_date = models.DateField(null=True, blank=True)
file = models.FileField(null=True, blank=True, upload_to=invoice_filename)
internal_reference = models.TextField(blank=True)

View File

@@ -8,6 +8,7 @@ from i18nfield.fields import I18nCharField
from pretix.base.decimal import round_decimal
from pretix.base.models.base import LoggedModel
from pretix.base.templatetags.money import money_filter
class TaxedPrice:
@@ -23,6 +24,13 @@ class TaxedPrice:
def __repr__(self):
return '{} + {}% = {}'.format(localize(self.net), localize(self.rate), localize(self.gross))
def print(self, currency):
return '{} + {}% = {}'.format(
money_filter(self.net, currency),
localize(self.rate),
money_filter(self.gross, currency)
)
TAXED_ZERO = TaxedPrice(
gross=Decimal('0.00'),
@@ -129,10 +137,12 @@ class TaxRule(LoggedModel):
if base_price_is == 'gross':
gross = base_price
net = gross - round_decimal(base_price * (1 - 100 / (100 + self.rate)))
net = round_decimal(gross - (base_price * (1 - 100 / (100 + self.rate))),
self.event.currency if self.event else None)
elif base_price_is == 'net':
net = base_price
gross = round_decimal(net * (1 + self.rate / 100))
gross = round_decimal((net * (1 + self.rate / 100)),
self.event.currency if self.event else None)
else:
raise ValueError('Unknown base price type: {}'.format(base_price_is))

View File

@@ -1,4 +1,4 @@
from decimal import Decimal
from decimal import ROUND_HALF_UP, Decimal
from django.conf import settings
from django.core.exceptions import ValidationError
@@ -368,9 +368,15 @@ class Voucher(LoggedModel):
"""
if self.value is not None:
if self.price_mode == 'set':
return self.value
p = self.value
elif self.price_mode == 'subtract':
return max(original_price - self.value, Decimal('0.00'))
p = max(original_price - self.value, Decimal('0.00'))
elif self.price_mode == 'percent':
return round_decimal(original_price * (Decimal('100.00') - self.value) / Decimal('100.00'))
p = round_decimal(original_price * (Decimal('100.00') - self.value) / Decimal('100.00'))
else:
p = original_price
places = settings.CURRENCY_PLACES.get(self.event.currency, 2)
if places < 2:
return p.quantize(Decimal('1') / 10 ** places, ROUND_HALF_UP)
return p
return original_price

View File

@@ -2,11 +2,12 @@ import logging
from collections import OrderedDict, namedtuple
from django.dispatch import receiver
from django.utils.formats import date_format, localize
from django.utils.formats import date_format
from django.utils.translation import ugettext_lazy as _
from pretix.base.models import Event, LogEntry
from pretix.base.signals import register_notification_types
from pretix.base.templatetags.money import money_filter
from pretix.helpers.urls import build_absolute_uri
logger = logging.getLogger(__name__)
@@ -174,7 +175,7 @@ class ParametrizedOrderNotificationType(NotificationType):
url=order_url
)
n.add_attribute(_('Order code'), order.code)
n.add_attribute(_('Order total'), '{} {}'.format(localize(order.total), logentry.event.currency))
n.add_attribute(_('Order total'), money_filter(order.total, logentry.event.currency))
n.add_attribute(_('Order date'), date_format(order.datetime, 'SHORT_DATETIME_FORMAT'))
n.add_attribute(_('Order status'), order.get_status_display())
n.add_attribute(_('Order positions'), str(order.positions.count()))

View File

@@ -1,10 +1,11 @@
import logging
from collections import OrderedDict
from decimal import Decimal
from decimal import ROUND_HALF_UP, Decimal
from typing import Any, Dict, Union
import pytz
from django import forms
from django.conf import settings
from django.contrib import messages
from django.dispatch import receiver
from django.forms import Form
@@ -15,11 +16,11 @@ from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from i18nfield.forms import I18nFormField, I18nTextarea
from i18nfield.strings import LazyI18nString
from pretix.base.decimal import round_decimal
from pretix.base.models import CartPosition, Event, Order, Quota
from pretix.base.reldate import RelativeDateField, RelativeDateWrapper
from pretix.base.settings import SettingsSandbox
from pretix.base.signals import register_payment_providers
from pretix.helpers.money import DecimalTextInput
from pretix.presale.views import get_cart_total
from pretix.presale.views.cart import get_or_create_cart_id
@@ -91,10 +92,15 @@ class BasePaymentProvider:
fee_abs = self.settings.get('_fee_abs', as_type=Decimal, default=0)
fee_percent = self.settings.get('_fee_percent', as_type=Decimal, default=0)
fee_reverse_calc = self.settings.get('_fee_reverse_calc', as_type=bool, default=True)
places = settings.CURRENCY_PLACES.get(self.event.currency, 2)
if fee_reverse_calc:
return round_decimal((price + fee_abs) * (1 / (1 - fee_percent / 100)) - price)
return ((price + fee_abs) * (1 / (1 - fee_percent / 100)) - price).quantize(
Decimal('1') / 10 ** places, ROUND_HALF_UP
)
else:
return round_decimal(price * fee_percent / 100) + fee_abs
return (price * fee_percent / 100 + fee_abs).quantize(
Decimal('1') / 10 ** places, ROUND_HALF_UP
)
@property
def verbose_name(self) -> str:
@@ -156,6 +162,7 @@ class BasePaymentProvider:
.. WARNING:: It is highly discouraged to alter the ``_enabled`` field of the default
implementation.
"""
places = settings.CURRENCY_PLACES.get(self.event.currency, 2)
return OrderedDict([
('_enabled',
forms.BooleanField(
@@ -166,7 +173,10 @@ class BasePaymentProvider:
forms.DecimalField(
label=_('Additional fee'),
help_text=_('Absolute value'),
required=False
localize=True,
required=False,
decimal_places=places,
widget=DecimalTextInput(places=places)
)),
('_fee_percent',
forms.DecimalField(
@@ -174,7 +184,8 @@ class BasePaymentProvider:
help_text=_('Percentage of the order total. Note that this percentage will currently only '
'be calculated on the summed price of sold tickets, not on other fees like e.g. shipping '
'fees, if there are any.'),
required=False
localize=True,
required=False,
)),
('_availability_date',
RelativeDateField(

View File

@@ -17,7 +17,7 @@ from django.utils.timezone import make_aware, now
from django.utils.translation import ugettext as _
from pretix.base.i18n import (
LazyDate, LazyLocaleException, LazyNumber, language,
LazyCurrencyNumber, LazyDate, LazyLocaleException, LazyNumber, language,
)
from pretix.base.models import (
CartPosition, Event, Item, ItemVariation, Order, OrderPosition, Quota,
@@ -504,6 +504,8 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d
for fee in fees:
fee.order = order
fee._calculate_tax()
if not fee.tax_rule.pk:
fee.tax_rule = None # TODO: deprecate
fee.save()
OrderPosition.transform_cart_positions(positions, order)
@@ -568,6 +570,7 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
email_context = {
'total': LazyNumber(order.total),
'currency': event.currency,
'total_with_currency': LazyCurrencyNumber(order.total, event.currency),
'date': LazyDate(order.expires),
'event': event.name,
'url': build_absolute_uri(event, 'presale:event.order', kwargs={

View File

@@ -1,5 +1,6 @@
from decimal import Decimal
from pretix.base.decimal import round_decimal
from pretix.base.models import (
AbstractPosition, InvoiceAddress, Item, ItemAddOn, ItemVariation, Voucher,
)
@@ -59,4 +60,8 @@ def get_price(item: Item, variation: ItemVariation = None,
price.gross = price.net
price.name = ''
price.gross = round_decimal(price.gross, item.event.currency)
price.net = round_decimal(price.net, item.event.currency)
price.tax = price.gross - price.net
return price

View File

@@ -276,7 +276,7 @@ Your {event} team"""))
'default': LazyI18nString.from_gettext(ugettext_noop("""Hello,
we successfully received your order for {event} with a total value
of {total} {currency}. Please complete your payment before {date}.
of {total_with_currency}. Please complete your payment before {date}.
{payment_info}

View File

@@ -0,0 +1,55 @@
from decimal import ROUND_HALF_UP, Decimal
from babel.numbers import format_currency
from django import template
from django.conf import settings
from django.template.defaultfilters import floatformat
from django.utils import translation
register = template.Library()
@register.filter("money")
def money_filter(value: Decimal, arg='', hide_currency=False):
if isinstance(value, float) or isinstance(value, int):
value = Decimal(value)
if not isinstance(value, Decimal):
raise TypeError("Invalid data type passed to money filter: %r" % type(value))
if not arg:
raise ValueError("No currency passed.")
places = settings.CURRENCY_PLACES.get(arg, 2)
rounded = value.quantize(Decimal('1') / 10 ** places, ROUND_HALF_UP)
if places < 2 and rounded != value:
places = 2
if hide_currency:
return floatformat(value, places)
try:
if rounded != value:
# We display decimal places even if we shouldn't for this currency if rounding
# would make the numbers incorrect. If this branch executes, it's likely a bug in
# pretix, but we won't show wrong numbers!
return '{} {}'.format(
arg,
floatformat(value, 2)
)
return format_currency(value, arg, locale=translation.get_language())
except:
return '{} {}'.format(
arg,
floatformat(value, places)
)
@register.filter("money_numberfield")
def money_numberfield_filter(value: Decimal, arg=''):
if isinstance(value, float) or isinstance(value, int):
value = Decimal(value)
if not isinstance(value, Decimal):
raise TypeError("Invalid data type passed to money filter: %r" % type(value))
if not arg:
raise ValueError("No currency passed.")
places = settings.CURRENCY_PLACES.get(arg, 2)
return str(value.quantize(Decimal('1') / 10 ** places, ROUND_HALF_UP))

View File

@@ -666,10 +666,10 @@ class MailSettingsForm(SettingsForm):
label=_("Text"),
required=False,
widget=I18nTextarea,
help_text=_("Available placeholders: {event}, {total}, {currency}, {date}, {payment_info}, {url}, "
"{invoice_name}, {invoice_company}"),
validators=[PlaceholderValidator(['{event}', '{total}', '{currency}', '{date}', '{payment_info}',
'{url}', '{invoice_name}', '{invoice_company}'])]
help_text=_("Available placeholders: {event}, {total_with_currency}, {total}, {currency}, {date}, "
"{payment_info}, {url}, {invoice_name}, {invoice_company}"),
validators=[PlaceholderValidator(['{event}', '{total_with_currency}', '{total}', '{currency}', '{date}',
'{payment_info}', '{url}', '{invoice_name}', '{invoice_company}'])]
)
mail_text_order_paid = I18nFormField(
label=_("Text"),

View File

@@ -17,6 +17,7 @@ from pretix.base.models import (
from pretix.base.models.items import ItemAddOn
from pretix.control.forms import SplitDateTimePickerWidget
from pretix.control.forms.widgets import Select2
from pretix.helpers.money import change_decimal_field
class CategoryForm(I18nModelForm):
@@ -159,6 +160,7 @@ class ItemCreateForm(I18nModelForm):
self.fields['category'].queryset = self.instance.event.categories.all()
self.fields['tax_rule'].queryset = self.instance.event.tax_rules.all()
change_decimal_field(self.fields['default_price'], self.instance.event.currency)
self.fields['tax_rule'].empty_label = _('No taxation')
self.fields['copy_from'] = forms.ModelChoiceField(
label=_("Copy product information"),
@@ -292,6 +294,7 @@ class ItemUpdateForm(I18nModelForm):
'over 65. This ticket includes access to all parts of the event, except the VIP '
'area.'
)
change_decimal_field(self.fields['default_price'], self.event.currency)
class Meta:
model = Item
@@ -345,8 +348,29 @@ class ItemVariationsFormSet(I18nFormSet):
return False
return form.cleaned_data.get(DELETION_FIELD_NAME, False)
def _construct_form(self, i, **kwargs):
kwargs['event'] = self.event
return super()._construct_form(i, **kwargs)
@property
def empty_form(self):
self.is_valid()
form = self.form(
auto_id=self.auto_id,
prefix=self.add_prefix('__prefix__'),
empty_permitted=True,
locales=self.locales,
event=self.event
)
self.add_fields(form, None)
return form
class ItemVariationForm(I18nModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
change_decimal_field(self.fields['default_price'], self.event.currency)
class Meta:
model = ItemVariation
localized_fields = '__all__'
@@ -399,7 +423,6 @@ class ItemAddOnsFormSet(I18nFormSet):
class ItemAddOnForm(I18nModelForm):
def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event')
super().__init__(*args, **kwargs)
self.fields['addon_category'].queryset = self.event.categories.all()

View File

@@ -2,7 +2,6 @@ from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.formats import localize
from django.utils.timezone import now
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
@@ -12,6 +11,8 @@ from pretix.base.models import (
)
from pretix.base.models.event import SubEvent
from pretix.base.services.pricing import get_price
from pretix.base.templatetags.money import money_filter
from pretix.helpers.money import change_decimal_field
class ExtendForm(I18nModelForm):
@@ -76,8 +77,8 @@ class SubEventChoiceField(forms.ModelChoiceField):
p = get_price(self.instance.item, self.instance.variation,
voucher=self.instance.voucher,
subevent=obj)
return '{} {} ({} {})'.format(obj.name, obj.get_date_range_display(),
p, self.instance.order.event.currency)
return '{} {} ({})'.format(obj.name, obj.get_date_range_display(),
money_filter(p, self.instance.order.event.currency))
class OtherOperationsForm(forms.Form):
@@ -120,6 +121,7 @@ class OrderPositionAddForm(forms.Form):
price = forms.DecimalField(
required=False,
max_digits=10, decimal_places=2,
localize=True,
label=_('Gross price'),
help_text=_("Including taxes, if any. Keep empty for the product's default price")
)
@@ -149,10 +151,10 @@ class OrderPositionAddForm(forms.Form):
for v in variations:
p = get_price(i, v, invoice_address=ia)
choices.append(('%d-%d' % (i.pk, v.pk),
'%s %s (%s %s)' % (pname, v.value, p, order.event.currency)))
'%s %s (%s)' % (pname, v.value, p.print(order.event.currency))))
else:
p = get_price(i, invoice_address=ia)
choices.append((str(i.pk), '%s (%s %s)' % (pname, p, order.event.currency)))
choices.append((str(i.pk), '%s (%s)' % (pname, p.print(order.event.currency))))
self.fields['itemvar'].choices = choices
if ItemAddOn.objects.filter(base_item__event=order.event).exists():
self.fields['addon_to'].queryset = order.positions.filter(addon_to__isnull=True).select_related(
@@ -165,6 +167,7 @@ class OrderPositionAddForm(forms.Form):
self.fields['subevent'].queryset = order.event.subevents.all()
else:
del self.fields['subevent']
change_decimal_field(self.fields['price'], order.event.currency)
class OrderPositionChangeForm(forms.Form):
@@ -178,6 +181,7 @@ class OrderPositionChangeForm(forms.Form):
price = forms.DecimalField(
required=False,
max_digits=10, decimal_places=2,
localize=True,
label=_('New price (gross)')
)
operation = forms.ChoiceField(
@@ -236,14 +240,13 @@ class OrderPositionChangeForm(forms.Form):
p = get_price(i, v, voucher=instance.voucher, subevent=instance.subevent,
invoice_address=ia)
choices.append(('%d-%d' % (i.pk, v.pk),
'%s %s (%s %s)' % (pname, v.value, localize(p),
instance.order.event.currency)))
'%s %s (%s)' % (pname, v.value, p.print(instance.order.event.currency))))
else:
p = get_price(i, None, voucher=instance.voucher, subevent=instance.subevent,
invoice_address=ia)
choices.append((str(i.pk), '%s (%s %s)' % (pname, localize(p),
instance.order.event.currency)))
choices.append((str(i.pk), '%s (%s)' % (pname, p.print(instance.order.event.currency))))
self.fields['itemvar'].choices = choices
change_decimal_field(self.fields['price'], instance.order.event.currency)
def clean(self):
if self.cleaned_data.get('operation') == 'price' and not self.cleaned_data.get('price', '') != '':

View File

@@ -5,7 +5,9 @@ from i18nfield.forms import I18nInlineFormSet
from pretix.base.forms import I18nModelForm
from pretix.base.models.event import SubEvent, SubEventMetaValue
from pretix.base.models.items import SubEventItem
from pretix.base.templatetags.money import money_filter
from pretix.control.forms import SplitDateTimePickerWidget
from pretix.helpers.money import change_decimal_field
class SubEventForm(I18nModelForm):
@@ -49,32 +51,35 @@ class SubEventItemOrVariationFormMixin:
self.item = kwargs.pop('item')
self.variation = kwargs.pop('variation', None)
super().__init__(*args, **kwargs)
change_decimal_field(self.fields['price'], self.item.event.currency)
class SubEventItemForm(SubEventItemOrVariationFormMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['price'].widget.attrs['placeholder'] = '{} {}'.format(
self.item.default_price, self.item.event.currency
)
self.fields['price'].widget.attrs['placeholder'] = money_filter(self.item.default_price, self.item.event.currency, hide_currency=True)
self.fields['price'].label = str(self.item.name)
class Meta:
model = SubEventItem
fields = ['price']
widgets = {
'price': forms.TextInput
}
class SubEventItemVariationForm(SubEventItemOrVariationFormMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['price'].widget.attrs['placeholder'] = '{} {}'.format(
self.variation.price, self.item.event.currency
)
self.fields['price'].widget.attrs['placeholder'] = money_filter(self.variation.price, self.item.event.currency, hide_currency=True)
self.fields['price'].label = '{} {}'.format(str(self.item.name), self.variation.value)
class Meta:
model = SubEventItem
fields = ['price']
widgets = {
'price': forms.TextInput
}
class QuotaFormSet(I18nInlineFormSet):

View File

@@ -4,7 +4,6 @@ from decimal import Decimal
import dateutil.parser
import pytz
from django.dispatch import receiver
from django.utils import formats
from django.utils.formats import date_format
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from i18nfield.strings import LazyI18nString
@@ -13,6 +12,7 @@ from pretix.base.models import (
CheckinList, Event, ItemVariation, LogEntry, OrderPosition,
)
from pretix.base.signals import logentry_display
from pretix.base.templatetags.money import money_filter
OVERVIEW_BLACKLIST = [
'pretix.plugins.sendmail.order.email.sent'
@@ -30,42 +30,38 @@ def _display_order_changed(event: Event, logentry: LogEntry):
new_item = str(event.items.get(pk=data['new_item']))
if data['new_variation']:
new_item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['new_variation']))
return text + ' ' + _('Position #{posid}: {old_item} ({old_price} {currency}) changed '
'to {new_item} ({new_price} {currency}).').format(
return text + ' ' + _('Position #{posid}: {old_item} ({old_price}) changed '
'to {new_item} ({new_price}).').format(
posid=data.get('positionid', '?'),
old_item=old_item, new_item=new_item,
old_price=formats.localize(Decimal(data['old_price'])),
new_price=formats.localize(Decimal(data['new_price'])),
currency=event.currency
old_price=money_filter(Decimal(data['old_price']), event.currency),
new_price=money_filter(Decimal(data['new_price']), event.currency),
)
elif logentry.action_type == 'pretix.event.order.changed.subevent':
old_se = str(event.subevents.get(pk=data['old_subevent']))
new_se = str(event.subevents.get(pk=data['new_subevent']))
return text + ' ' + _('Position #{posid}: Event date "{old_event}" ({old_price} {currency}) changed '
'to "{new_event}" ({new_price} {currency}).').format(
return text + ' ' + _('Position #{posid}: Event date "{old_event}" ({old_price}) changed '
'to "{new_event}" ({new_price}).').format(
posid=data.get('positionid', '?'),
old_event=old_se, new_event=new_se,
old_price=formats.localize(Decimal(data['old_price'])),
new_price=formats.localize(Decimal(data['new_price'])),
currency=event.currency
old_price=money_filter(Decimal(data['old_price']), event.currency),
new_price=money_filter(Decimal(data['new_price']), event.currency),
)
elif logentry.action_type == 'pretix.event.order.changed.price':
return text + ' ' + _('Price of position #{posid} changed from {old_price} {currency} '
'to {new_price} {currency}.').format(
return text + ' ' + _('Price of position #{posid} changed from {old_price} '
'to {new_price}.').format(
posid=data.get('positionid', '?'),
old_price=formats.localize(Decimal(data['old_price'])),
new_price=formats.localize(Decimal(data['new_price'])),
currency=event.currency
old_price=money_filter(Decimal(data['old_price']), event.currency),
new_price=money_filter(Decimal(data['new_price']), event.currency),
)
elif logentry.action_type == 'pretix.event.order.changed.cancel':
old_item = str(event.items.get(pk=data['old_item']))
if data['old_variation']:
old_item += ' - ' + str(ItemVariation.objects.get(pk=data['old_variation']))
return text + ' ' + _('Position #{posid} ({old_item}, {old_price} {currency}) removed.').format(
return text + ' ' + _('Position #{posid} ({old_item}, {old_price}) removed.').format(
posid=data.get('positionid', '?'),
old_item=old_item,
old_price=formats.localize(Decimal(data['old_price'])),
currency=event.currency
old_price=money_filter(Decimal(data['old_price']), event.currency),
)
elif logentry.action_type == 'pretix.event.order.changed.add':
item = str(event.items.get(pk=data['item']))
@@ -73,30 +69,27 @@ def _display_order_changed(event: Event, logentry: LogEntry):
item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['variation']))
if data['addon_to']:
addon_to = OrderPosition.objects.get(order__event=event, pk=data['addon_to'])
return text + ' ' + _('Position #{posid} created: {item} ({price} {currency}) as an add-on to '
return text + ' ' + _('Position #{posid} created: {item} ({price}) as an add-on to '
'position #{addon_to}.').format(
posid=data.get('positionid', '?'),
item=item, addon_to=addon_to.positionid,
price=formats.localize(Decimal(data['price'])),
currency=event.currency
price=money_filter(Decimal(data['price']), event.currency),
)
else:
return text + ' ' + _('Position #{posid} created: {item} ({price} {currency}).').format(
return text + ' ' + _('Position #{posid} created: {item} ({price}).').format(
posid=data.get('positionid', '?'),
item=item,
price=formats.localize(Decimal(data['price'])),
currency=event.currency
price=money_filter(Decimal(data['price']), event.currency),
)
elif logentry.action_type == 'pretix.event.order.changed.split':
old_item = str(event.items.get(pk=data['old_item']))
if data['old_variation']:
old_item += ' - ' + str(ItemVariation.objects.get(pk=data['old_variation']))
return text + ' ' + _('Position #{posid} ({old_item}, {old_price} {currency}) split into new order: {order}').format(
return text + ' ' + _('Position #{posid} ({old_item}, {old_price}) split into new order: {order}').format(
old_item=old_item,
posid=data.get('positionid', '?'),
order=data['new_order'],
old_price=formats.localize(Decimal(data['old_price'])),
currency=event.currency
old_price=money_filter(Decimal(data['old_price']), event.currency),
)
elif logentry.action_type == 'pretix.event.order.changed.split_from':
return _('This order has been created by splitting the order {order}').format(

View File

@@ -2,6 +2,7 @@
{% load i18n %}
{% load bootstrap3 %}
{% load eventurl %}
{% load money %}
{% load safelink %}
{% load eventsignal %}
{% block title %}
@@ -257,7 +258,7 @@
</div>
<div class="col-md-3 col-xs-6 price">
{% if event.settings.display_net_prices %}
<strong>{{ event.currency }} {{ line.net_price|floatformat:2 }}</strong>
<strong>{{ line.net_price|money:event.currency }}</strong>
{% if line.tax_rate %}
<br />
<small>
@@ -267,7 +268,7 @@
</small>
{% endif %}
{% else %}
<strong>{{ event.currency }} {{ line.price|floatformat:2 }}</strong>
<strong>{{ line.price|money:event.currency }}</strong>
{% if line.tax_rate and line.price %}
<br />
<small>
@@ -291,7 +292,7 @@
</div>
<div class="col-md-3 col-xs-6 col-md-offset-5 price">
{% if event.settings.display_net_prices %}
<strong>{{ event.currency }} {{ fee.net_value|floatformat:2 }}</strong>
<strong>{{ fee.net_value|money:event.currency }}</strong>
{% if fee.tax_rate %}
<br/>
<small>
@@ -301,7 +302,7 @@
</small>
{% endif %}
{% else %}
<strong>{{ event.currency }} {{ fee.value|floatformat:2 }}</strong>
<strong>{{ fee.value|money:event.currency }}</strong>
{% if fee.tax_rate %}
<br/>
<small>
@@ -321,7 +322,7 @@
<strong>{% trans "Net total" %}</strong>
</div>
<div class="col-md-3 col-xs-6 col-md-offset-5 price">
{{ event.currency }} {{ items.net_total|floatformat:2 }}
{{ items.net_total|money:event.currency }}
</div>
<div class="clearfix"></div>
</div>
@@ -330,7 +331,7 @@
<strong>{% trans "Taxes" %}</strong>
</div>
<div class="col-md-3 col-xs-6 col-md-offset-5 price">
{{ event.currency }} {{ items.tax_total|floatformat:2 }}
{{ items.tax_total|money:event.currency }}
</div>
<div class="clearfix"></div>
</div>
@@ -340,7 +341,7 @@
<strong>{% trans "Total" %}</strong>
</div>
<div class="col-md-3 col-xs-6 col-md-offset-5 price">
<strong>{{ event.currency }} {{ items.total|floatformat:2 }}</strong>
<strong>{{ items.total|money:event.currency }}</strong>
</div>
<div class="clearfix"></div>
</div>

View File

@@ -2,6 +2,7 @@
{% load i18n %}
{% load eventurl %}
{% load urlreplace %}
{% load money %}
{% load bootstrap3 %}
{% block title %}{% trans "Orders" %}{% endblock %}
{% block content %}
@@ -114,7 +115,7 @@
{% endif %}
</td>
<td>{{ o.datetime|date:"SHORT_DATETIME_FORMAT" }}</td>
<td class="text-right">{{ o.total|floatformat:2 }} {{ request.event.currency }}</td>
<td class="text-right">{{ o.total|money:request.event.currency }}</td>
<td class="text-right">{{ o.pcnt }}</td>
<td class="text-right">{% include "pretixcontrol/orders/fragment_order_status.html" with order=o %}</td>
</tr>

View File

@@ -50,12 +50,12 @@
{% if tup.0 %}
<tr class="category">
<th>{{ tup.0.name }}</th>
<th>{{ tup.0.num_canceled|togglesum }}</th>
<th>{{ tup.0.num_refunded|togglesum }}</th>
<th>{{ tup.0.num_expired|togglesum }}</th>
<th>{{ tup.0.num_pending|togglesum }}</th>
<th>{{ tup.0.num_paid|togglesum }}</th>
<th>{{ tup.0.num_total|togglesum }}</th>
<th>{{ tup.0.num_canceled|togglesum:request.event.currency }}</th>
<th>{{ tup.0.num_refunded|togglesum:request.event.currency }}</th>
<th>{{ tup.0.num_expired|togglesum:request.event.currency }}</th>
<th>{{ tup.0.num_pending|togglesum:request.event.currency }}</th>
<th>{{ tup.0.num_paid|togglesum:request.event.currency }}</th>
<th>{{ tup.0.num_total|togglesum:request.event.currency }}</th>
</tr>
{% endif %}
{% for item in tup.1 %}
@@ -63,43 +63,43 @@
<td>{{ item.name }}</td>
<td>
<a href="{{ listurl }}?item={{ item.id }}&amp;status=c&amp;provider={{ item.provider }}">
{{ item.num_canceled|togglesum }}
{{ item.num_canceled|togglesum:request.event.currency }}
</a>
</td>
<td>
<a href="{{ listurl }}?item={{ item.id }}&amp;status=r&amp;provider={{ item.provider }}">
{{ item.num_refunded|togglesum }}
{{ item.num_refunded|togglesum:request.event.currency }}
</a>
</td>
<td>
<a href="{{ listurl }}?item={{ item.id }}&amp;status=e&amp;provider={{ item.provider }}">
{{ item.num_expired|togglesum }}
{{ item.num_expired|togglesum:request.event.currency }}
</a>
</td>
<td>
<a href="{{ listurl }}?item={{ item.id }}&amp;status=n&amp;provider={{ item.provider }}">
{{ item.num_pending|togglesum }}
{{ item.num_pending|togglesum:request.event.currency }}
</a>
</td>
<td>
<a href="{{ listurl }}?item={{ item.id }}&amp;status=p&amp;provider={{ item.provider }}">
{{ item.num_paid|togglesum }}
{{ item.num_paid|togglesum:request.event.currency }}
</a>
</td>
<td>
{{ item.num_total|togglesum }}
{{ item.num_total|togglesum:request.event.currency }}
</td>
</tr>
{% if item.has_variations %}
{% for var in item.all_variations %}
<tr class="variation {% if tup.0 %}categorized{% endif %}">
<td>{{ var }}</td>
<td>{{ var.num_canceled|togglesum }}</td>
<td>{{ var.num_refunded|togglesum }}</td>
<td>{{ var.num_expired|togglesum }}</td>
<td>{{ var.num_pending|togglesum }}</td>
<td>{{ var.num_paid|togglesum }}</td>
<td>{{ var.num_total|togglesum }}</td>
<td>{{ var.num_canceled|togglesum:request.event.currency }}</td>
<td>{{ var.num_refunded|togglesum:request.event.currency }}</td>
<td>{{ var.num_expired|togglesum:request.event.currency }}</td>
<td>{{ var.num_pending|togglesum:request.event.currency }}</td>
<td>{{ var.num_paid|togglesum:request.event.currency }}</td>
<td>{{ var.num_total|togglesum:request.event.currency }}</td>
</tr>
{% endfor %}
{% endif %}
@@ -109,12 +109,12 @@
<tfoot>
<tr class="total">
<th>{% trans "Total" %}</th>
<th>{{ total.num_canceled|togglesum }}</th>
<th>{{ total.num_refunded|togglesum }}</th>
<th>{{ total.num_expired|togglesum }}</th>
<th>{{ total.num_pending|togglesum }}</th>
<th>{{ total.num_paid|togglesum }}</th>
<th>{{ total.num_total|togglesum }}</th>
<th>{{ total.num_canceled|togglesum:request.event.currency }}</th>
<th>{{ total.num_refunded|togglesum:request.event.currency }}</th>
<th>{{ total.num_expired|togglesum:request.event.currency }}</th>
<th>{{ total.num_pending|togglesum:request.event.currency }}</th>
<th>{{ total.num_paid|togglesum:request.event.currency }}</th>
<th>{{ total.num_total|togglesum:request.event.currency }}</th>
</tr>
</tfoot>
</table>

View File

@@ -2,6 +2,7 @@
{% load i18n %}
{% load eventurl %}
{% load urlreplace %}
{% load money %}
{% load bootstrap3 %}
{% block title %}{% trans "Order search" %}{% endblock %}
{% block content %}
@@ -70,7 +71,7 @@
{% endif %}
</td>
<td>{{ o.datetime|date:"SHORT_DATETIME_FORMAT" }}</td>
<td class="text-right">{{ o.total|floatformat:2 }} {{ o.event.currency }}</td>
<td class="text-right">{{ o.total|money:o.event.currency }}</td>
<td class="text-right">{% include "pretixcontrol/orders/fragment_order_status.html" with order=o %}</td>
</tr>
{% empty %}

View File

@@ -120,7 +120,7 @@
<fieldset>
<legend>{% trans "Item prices" %}</legend>
{% for f in itemvar_forms %}
{% bootstrap_field f.price layout="control" %}
{% bootstrap_field f.price addon_after=request.event.currency layout="control" %}
{% endfor %}
</fieldset>
<fieldset>

View File

@@ -1,6 +1,7 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% load eventurl %}
{% load money %}
{% load urlreplace %}
{% block title %}{% trans "Waiting list" %}{% endblock %}
{% block content %}
@@ -65,9 +66,9 @@
{% trans "Sales estimate" %}
</div>
<div class="panel-body">
{% blocktrans trimmed with amount=estimate|default:0|floatformat:2 currency=request.event.currency %}
{% blocktrans trimmed with amount=estimate|default:0|money:request.event.currency %}
If you can make enough room at your event to fit all the persons on the waiting list in, you
could sell tickets worth an additional <strong>{{ amount }} {{ currency }}</strong>.
could sell tickets worth an additional <strong>{{ amount }}</strong>.
{% endblocktrans %}
</div>
</div>

View File

@@ -1,5 +1,6 @@
from django import template
from django.utils import formats
from django.conf import settings
from django.template.defaultfilters import floatformat
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe
@@ -7,7 +8,7 @@ register = template.Library()
@register.filter(name='togglesum', needs_autoescape=True)
def cut(value, autoescape=True):
def togglesum_filter(value, arg='EUR', autoescape=True):
def noop(x):
return x
@@ -17,6 +18,10 @@ def cut(value, autoescape=True):
esc = conditional_escape
else:
esc = noop
places = settings.CURRENCY_PLACES.get(arg, 2)
return mark_safe('<span class="count">{0}</span><span class="sum-gross">{1}</span><span class="sum-net">{2}</span>'.format(
esc(value[0]), esc(formats.localize(value[1])), esc(formats.localize(value[2]))
esc(value[0]),
esc(floatformat(value[1], places)),
esc(floatformat(value[2], places))
))

View File

@@ -1,6 +1,7 @@
import re
from collections import OrderedDict
from datetime import timedelta
from decimal import Decimal
from urllib.parse import urlsplit
from django.conf import settings
@@ -26,6 +27,7 @@ from django.views.generic.detail import SingleObjectMixin
from i18nfield.strings import LazyI18nString
from pytz import timezone
from pretix.base.i18n import LazyCurrencyNumber
from pretix.base.models import (
CachedCombinedTicket, CachedTicket, Event, Item, ItemVariation, LogEntry,
Order, RequiredAction, TaxRule, Voucher,
@@ -34,6 +36,7 @@ from pretix.base.models.event import EventMetaValue
from pretix.base.services import tickets
from pretix.base.services.invoices import build_preview_invoice_pdf
from pretix.base.signals import event_live_issues, register_ticket_outputs
from pretix.base.templatetags.money import money_filter
from pretix.control.forms.event import (
CommentForm, DisplaySettingsForm, EventDeleteForm, EventMetaValueForm,
EventSettingsForm, EventUpdateForm, InvoiceSettingsForm, MailSettingsForm,
@@ -492,8 +495,8 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
return {
'date': date_format(now() + timedelta(days=7), 'SHORT_DATE_FORMAT'),
'expire_date': date_format(now() + timedelta(days=15), 'SHORT_DATE_FORMAT'),
'payment_info': _('{} {} has been transferred to account <9999-9999-9999-9999> at {}').format(
42.23, self.request.event.currency, date_format(now(), 'SHORT_DATETIME_FORMAT'))
'payment_info': _('{} has been transferred to account <9999-9999-9999-9999> at {}').format(
money_filter(Decimal('42.23'), self.request.event.currency), date_format(now(), 'SHORT_DATETIME_FORMAT'))
}
# create index-language mapping
@@ -508,7 +511,7 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
@cached_property
def items(self):
return {
'mail_text_order_placed': ['total', 'currency', 'date', 'invoice_company',
'mail_text_order_placed': ['total', 'currency', 'date', 'invoice_company', 'total_with_currency',
'event', 'payment_info', 'url', 'invoice_name'],
'mail_text_order_paid': ['event', 'url', 'invoice_name', 'invoice_company', 'payment_info'],
'mail_text_order_free': ['event', 'url', 'invoice_name', 'invoice_company'],
@@ -536,6 +539,7 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
return {
'event': self.request.event.name,
'total': 42.23,
'total_with_currency': LazyCurrencyNumber(42.23, self.request.event.currency),
'currency': self.request.event.currency,
'url': self.generate_order_url(user_orders[0]['code'], user_orders[0]['secret']),
'orders': '\n'.join(orders),

View File

@@ -0,0 +1,34 @@
from decimal import Decimal
from django.conf import settings
from django.core.validators import DecimalValidator
from django.forms import NumberInput, TextInput
from django.utils import formats
class DecimalTextInput(TextInput):
def __init__(self, *args, **kwargs):
self.places = kwargs.pop('places', 2)
super().__init__(*args, **kwargs)
def format_value(self, value):
"""
Return a value as it should appear when rendered in a template.
"""
if value == '' or value is None:
return None
if isinstance(value, str):
return value
return formats.localize_input(value.quantize(Decimal('1') / 10 ** self.places))
def change_decimal_field(field, currency):
places = settings.CURRENCY_PLACES.get(currency, 2)
field.decimal_places = places
if isinstance(field.widget, NumberInput):
field.widget.attrs['step'] = str(Decimal('1') / 10 ** places).lower()
elif isinstance(field.widget, TextInput):
field.widget = DecimalTextInput(places=places)
v = [v for v in field.validators if isinstance(v, DecimalValidator)]
if len(v) == 1:
v[0].decimal_places = places

View File

@@ -1,7 +1,7 @@
{% load i18n %}{% load l10n %}{% blocktrans with bank=details|safe code=order.full_code total=order.total|localize currency=event.currency %}
{% load i18n %}{% load l10n %}{% load money %}{% blocktrans with bank=details|safe code=order.full_code total=order.total|money:event.currency %}
Please transfer the full amount to the following bank account.
Reference: {{ code }}
Amount: {{ total }} {{ currency }}
Amount: {{ total }}
{{ bank }}
{% endblocktrans %}

View File

@@ -1,5 +1,6 @@
{% load i18n %}
{% load l10n %}
{% load money %}
<p>{% blocktrans trimmed %}
Please transfer the full amount to the following bank account:
@@ -7,6 +8,6 @@
<address>
{{ details|linebreaksbr }}<br />
{% trans "Amount:" %} {{ order.total|localize }} {{ event.currency }}<br />
{% trans "Amount:" %} {{ order.total|money:event.currency }}<br />
<strong>{% trans "Reference code (important):" %} {{ order.full_code }}</strong>
</address>

View File

@@ -1,5 +1,6 @@
{% load i18n %}
{% load rich_text %}
{% load money %}
{% load staticfiles %}
<div class="table-responsive">
{% csrf_token %}
@@ -85,7 +86,7 @@
<td>
{% if trans.order %}
<a href="{% url "control:event.order" event=trans.order.event.slug organizer=request.organizer.slug code=trans.order.code %}"
data-toggle="tooltip" title="{{ trans.order.total|floatformat:2 }} {{ trans.order.event.currency }}">
data-toggle="tooltip" title="{{ trans.order.total|money:trans.order.event.currency }}">
{% if not request.event %}
{{ trans.order.event.slug|upper }}-{{ trans.order.code }}
{% else %}

View File

@@ -18,6 +18,7 @@ from pretix.base.models import Order, Quota
from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import mark_order_paid
from pretix.base.settings import SettingsSandbox
from pretix.base.templatetags.money import money_filter
from pretix.control.permissions import (
EventPermissionRequiredMixin, OrganizerPermissionRequiredMixin,
)
@@ -147,8 +148,6 @@ class ActionView(View):
})
def get(self, request, *args, **kwargs):
from django.utils.formats import localize
u = request.GET.get('query', '')
if len(u) < 2:
return JsonResponse({'results': []})
@@ -178,7 +177,7 @@ class ActionView(View):
{
'code': o.event.slug.upper() + '-' + o.code,
'status': o.get_status_display(),
'total': localize(o.total) + ' ' + o.event.currency
'total': money_filter(o.total, o.event.currency)
} for o in qs
]
})

View File

@@ -6,7 +6,7 @@ from defusedcsv import csv
from django import forms
from django.db.models import Max, OuterRef, Subquery
from django.db.models.functions import Coalesce
from django.utils.formats import date_format, localize
from django.utils.formats import date_format
from django.utils.timezone import is_aware, make_aware
from django.utils.translation import pgettext, ugettext as _, ugettext_lazy
from pytz import UTC
@@ -15,6 +15,7 @@ from reportlab.platypus import Flowable, Paragraph, Spacer, Table, TableStyle
from pretix.base.exporter import BaseExporter
from pretix.base.models import Checkin, Order, OrderPosition, Question
from pretix.base.templatetags.money import money_filter
from pretix.plugins.reports.exporters import ReportlabExportMixin
@@ -200,7 +201,7 @@ class PDFCheckinList(ReportlabExportMixin, BaseCheckinList):
op.order.code,
name,
str(op.item.name) + (" " + str(op.variation.value) if op.variation else "") + "\n" +
self.event.currency + " " + localize(op.price),
money_filter(op.price, self.event.currency),
]
acache = {}
for a in op.answers.all():

View File

@@ -55,6 +55,8 @@ class Paypal(BasePaymentProvider):
('client_id',
forms.CharField(
label=_('Client ID'),
max_length=80,
min_length=80,
help_text=_('<a target="_blank" rel="noopener" href="{docs_url}">{text}</a>').format(
text=_('Click here for a tutorial on how to obtain the required keys'),
docs_url='https://docs.pretix.eu/en/latest/user/payments/paypal.html'
@@ -63,6 +65,8 @@ class Paypal(BasePaymentProvider):
('secret',
forms.CharField(
label=_('Secret'),
max_length=80,
min_length=80,
))
]
)

View File

@@ -7,7 +7,8 @@ from django import forms
from django.conf import settings
from django.contrib.staticfiles import finders
from django.db.models import Sum
from django.utils.formats import date_format, localize
from django.template.defaultfilters import floatformat
from django.utils.formats import date_format
from django.utils.timezone import get_current_timezone, now
from django.utils.translation import pgettext, pgettext_lazy, ugettext as _
@@ -194,49 +195,50 @@ class OverviewReport(Report):
]
items_by_category, total = order_overview(self.event, subevent=self.form_data.get('subevent'))
places = settings.CURRENCY_PLACES.get(self.event.currency, 2)
for tup in items_by_category:
if tup[0]:
tstyledata.append(('FONTNAME', (0, len(tdata)), (-1, len(tdata)), 'OpenSansBd'))
tdata.append([
tup[0].name,
str(tup[0].num_canceled[0]), localize(tup[0].num_canceled[1]),
str(tup[0].num_refunded[0]), localize(tup[0].num_refunded[1]),
str(tup[0].num_expired[0]), localize(tup[0].num_expired[1]),
str(tup[0].num_pending[0]), localize(tup[0].num_pending[1]),
str(tup[0].num_paid[0]), localize(tup[0].num_paid[1]),
str(tup[0].num_total[0]), localize(tup[0].num_total[1]),
str(tup[0].num_canceled[0]), floatformat(tup[0].num_canceled[1], places),
str(tup[0].num_refunded[0]), floatformat(tup[0].num_refunded[1], places),
str(tup[0].num_expired[0]), floatformat(tup[0].num_expired[1], places),
str(tup[0].num_pending[0]), floatformat(tup[0].num_pending[1], places),
str(tup[0].num_paid[0]), floatformat(tup[0].num_paid[1], places),
str(tup[0].num_total[0]), floatformat(tup[0].num_total[1], places),
])
for item in tup[1]:
tdata.append([
" " + str(item.name),
str(item.num_canceled[0]), localize(item.num_canceled[1]),
str(item.num_refunded[0]), localize(item.num_refunded[1]),
str(item.num_expired[0]), localize(item.num_expired[1]),
str(item.num_pending[0]), localize(item.num_pending[1]),
str(item.num_paid[0]), localize(item.num_paid[1]),
str(item.num_total[0]), localize(item.num_total[1]),
str(item.num_canceled[0]), floatformat(item.num_canceled[1], places),
str(item.num_refunded[0]), floatformat(item.num_refunded[1], places),
str(item.num_expired[0]), floatformat(item.num_expired[1], places),
str(item.num_pending[0]), floatformat(item.num_pending[1], places),
str(item.num_paid[0]), floatformat(item.num_paid[1], places),
str(item.num_total[0]), floatformat(item.num_total[1], places),
])
if item.has_variations:
for var in item.all_variations:
tdata.append([
" " + str(var),
str(var.num_canceled[0]), localize(var.num_canceled[1]),
str(var.num_refunded[0]), localize(var.num_refunded[1]),
str(var.num_expired[0]), localize(var.num_expired[1]),
str(var.num_pending[0]), localize(var.num_pending[1]),
str(var.num_paid[0]), localize(var.num_paid[1]),
str(var.num_total[0]), localize(var.num_total[1]),
str(var.num_canceled[0]), floatformat(var.num_canceled[1], places),
str(var.num_refunded[0]), floatformat(var.num_refunded[1], places),
str(var.num_expired[0]), floatformat(var.num_expired[1], places),
str(var.num_pending[0]), floatformat(var.num_pending[1], places),
str(var.num_paid[0]), floatformat(var.num_paid[1], places),
str(var.num_total[0]), floatformat(var.num_total[1], places),
])
tdata.append([
_("Total"),
str(total['num_canceled'][0]), localize(total['num_canceled'][1]),
str(total['num_refunded'][0]), localize(total['num_refunded'][1]),
str(total['num_expired'][0]), localize(total['num_expired'][1]),
str(total['num_pending'][0]), localize(total['num_pending'][1]),
str(total['num_paid'][0]), localize(total['num_paid'][1]),
str(total['num_total'][0]), localize(total['num_total'][1]),
str(total['num_canceled'][0]), floatformat(total['num_canceled'][1], places),
str(total['num_refunded'][0]), floatformat(total['num_refunded'][1], places),
str(total['num_expired'][0]), floatformat(total['num_expired'][1], places),
str(total['num_pending'][0]), floatformat(total['num_pending'][1], places),
str(total['num_paid'][0]), floatformat(total['num_paid'][1], places),
str(total['num_total'][0]), floatformat(total['num_total'][1], places),
])
table = Table(tdata, colWidths=colwidths, repeatRows=3)

View File

@@ -5,6 +5,7 @@ from collections import OrderedDict
import stripe
from django import forms
from django.conf import settings
from django.contrib import messages
from django.template.loader import get_template
from django.utils.translation import ugettext, ugettext_lazy as _
@@ -165,6 +166,10 @@ class StripeMethod(BasePaymentProvider):
def order_prepare(self, request, order):
return self.checkout_prepare(request, None)
def _get_amount(self, order):
places = settings.CURRENCY_PLACES.get(self.event.currency, 2)
return int(order.total * 10 ** places)
def _init_api(self):
stripe.api_version = '2017-06-05'
stripe.api_key = self.settings.get('secret_key')
@@ -180,7 +185,7 @@ class StripeMethod(BasePaymentProvider):
def _charge_source(self, request, source, order):
try:
charge = stripe.Charge.create(
amount=int(order.total * 100),
amount=self._get_amount(order),
currency=self.event.currency.lower(),
source=source,
metadata={
@@ -269,7 +274,7 @@ class StripeMethod(BasePaymentProvider):
if order.payment_info:
payment_info = json.loads(order.payment_info)
if 'amount' in payment_info:
payment_info['amount'] /= 100
payment_info['amount'] /= 10 ** settings.CURRENCY_PLACES.get(self.event.currency, 2)
else:
payment_info = None
template = get_template('pretixplugins/stripe/control.html')
@@ -411,7 +416,7 @@ class StripeCC(StripeMethod):
request.session['payment_stripe_order_secret'] = order.secret
source = stripe.Source.create(
type='three_d_secure',
amount=int(order.total * 100),
amount=self._get_amount(order),
currency=self.event.currency.lower(),
three_d_secure={
'card': src.id
@@ -479,7 +484,7 @@ class StripeGiropay(StripeMethod):
try:
source = stripe.Source.create(
type='giropay',
amount=int(order.total * 100),
amount=self._get_amount(order),
currency=self.event.currency.lower(),
metadata={
'order': str(order.id),
@@ -538,7 +543,7 @@ class StripeIdeal(StripeMethod):
def _create_source(self, request, order):
source = stripe.Source.create(
type='ideal',
amount=int(order.total * 100),
amount=self._get_amount(order),
currency=self.event.currency.lower(),
metadata={
'order': str(order.id),
@@ -585,7 +590,7 @@ class StripeAlipay(StripeMethod):
def _create_source(self, request, order):
source = stripe.Source.create(
type='alipay',
amount=int(order.total * 100),
amount=self._get_amount(order),
currency=self.event.currency.lower(),
metadata={
'order': str(order.id),
@@ -634,7 +639,7 @@ class StripeBancontact(StripeMethod):
try:
source = stripe.Source.create(
type='bancontact',
amount=int(order.total * 100),
amount=self._get_amount(order),
currency=self.event.currency.lower(),
metadata={
'order': str(order.id),
@@ -706,7 +711,7 @@ class StripeSofort(StripeMethod):
def _create_source(self, request, order):
source = stripe.Source.create(
type='sofort',
amount=int(order.total * 100),
amount=self._get_amount(order),
currency=self.event.currency.lower(),
metadata={
'order': str(order.id),

View File

@@ -11,7 +11,7 @@ from django.core.files import File
from django.core.files.storage import default_storage
from django.http import HttpRequest
from django.template.loader import get_template
from django.utils.formats import date_format, localize
from django.utils.formats import date_format
from django.utils.translation import ugettext_lazy as _
from pytz import timezone
from reportlab.graphics import renderPDF
@@ -29,6 +29,7 @@ from reportlab.platypus import Paragraph
from pretix.base.i18n import language
from pretix.base.models import Order, OrderPosition
from pretix.base.templatetags.money import money_filter
from pretix.base.ticketoutput import BaseTicketOutput
from pretix.plugins.ticketoutputpdf.signals import (
get_fonts, layout_text_variables,
@@ -81,7 +82,7 @@ DEFAULT_VARIABLES = OrderedDict((
("price", {
"label": _("Price"),
"editor_sample": _("123.45 EUR"),
"evaluate": lambda op, order, event: '{} {}'.format(event.currency, localize(op.price))
"evaluate": lambda op, order, event: money_filter(op.price, event.currency)
}),
("attendee_name", {
"label": _("Attendee name"),

View File

@@ -13,6 +13,7 @@ from pretix.base.forms.questions import (
)
from pretix.base.models import ItemVariation
from pretix.base.models.tax import TAXED_ZERO
from pretix.base.templatetags.money import money_filter
from pretix.base.templatetags.rich_text import rich_text
from pretix.base.validators import EmailBlacklistValidator
from pretix.presale.signals import contact_form_fields
@@ -133,17 +134,17 @@ class AddOnsForm(forms.Form):
name=label
)
elif not price.rate:
n = _('{name} (+ {currency} {price})').format(
name=label, currency=event.currency, price=number_format(price.gross)
n = _('{name} (+ {price})').format(
name=label, price=money_filter(price.gross, event.currency)
)
elif event.settings.display_net_prices:
n = _('{name} (+ {currency} {price} plus {taxes}% {taxname})').format(
name=label, currency=event.currency, price=number_format(price.net),
n = _('{name} (+ {price} plus {taxes}% {taxname})').format(
name=label, price=money_filter(price.net, event.currency),
taxes=number_format(price.rate), taxname=price.name
)
else:
n = _('{name} (+ {currency} {price} incl. {taxes}% {taxname})').format(
name=label, currency=event.currency, price=number_format(price.gross),
n = _('{name} (+ {price} incl. {taxes}% {taxname})').format(
name=label, price=money_filter(price.gross, event.currency),
taxes=number_format(price.rate), taxname=price.name
)

View File

@@ -1,5 +1,6 @@
{% extends "pretixpresale/event/checkout_base.html" %}
{% load i18n %}
{% load money %}
{% load bootstrap3 %}
{% block inner %}
<p>{% trans "Please select how you want to pay." %}</p>
@@ -12,7 +13,7 @@
<div class="panel-heading">
<h4 class="panel-title">
{% if show_fees %}
<strong class="pull-right">{% if p.fee < 0 %}-{% else %}+{% endif %} {{ p.fee|floatformat:2|cut:"-" }} {{ event.currency }}</strong>
<strong class="pull-right">{% if p.fee < 0 %}-{% else %}+{% endif %} {{ p.fee|money:event.currency|cut:"-" }}</strong>
{% endif %}
<input type="radio" name="payment" value="{{ p.provider.identifier }}"
data-parent="#payment_accordion"

View File

@@ -1,6 +1,7 @@
{% load i18n %}
{% load eventurl %}
{% load safelink %}
{% load money %}
{% blocktrans asvar s_taxes %}taxes{% endblocktrans %}
{% for line in cart.positions %}
<div class="row cart-row {% if download and line.item.admission|default:event.settings.ticket_download_nonadm %}has-downloads{% endif %}">
@@ -71,9 +72,9 @@
<div class="count">&nbsp;</div>
<div class="singleprice price">
{% if event.settings.display_net_prices %}
{{ event.currency }} {{ line.net_price|floatformat:2 }}
{{ line.net_price|money:event.currency }}
{% else %}
{{ event.currency }} {{ line.price|floatformat:2 }}
{{ line.price|money:event.currency }}
{% endif %}
</div>
{% else %}
@@ -113,15 +114,15 @@
</div>
<div class="singleprice price">
{% if event.settings.display_net_prices %}
{{ event.currency }} {{ line.net_price|floatformat:2 }}
{{ line.net_price|money:event.currency }}
{% else %}
{{ event.currency }} {{ line.price|floatformat:2 }}
{{ line.price|money:event.currency }}
{% endif %}
</div>
{% endif %}
<div class="totalprice price">
{% if event.settings.display_net_prices %}
<strong>{{ event.currency }} {{ line.net_total|floatformat:2 }}</strong>
<strong>{{ line.net_total|money:event.currency }}</strong>
{% if line.tax_rate and line.total %}
<br />
<small>
@@ -131,7 +132,7 @@
</small>
{% endif %}
{% else %}
<strong>{{ event.currency }} {{ line.total|floatformat:2 }}</strong>
<strong>{{ line.total|money:event.currency }}</strong>
{% if line.tax_rate and line.total %}
<br />
<small>
@@ -165,7 +166,7 @@
</div>
<div class="col-md-3 col-xs-6 col-md-offset-5 price">
{% if event.settings.display_net_prices %}
<strong>{{ event.currency }} {{ fee.net_value|floatformat:2 }}</strong>
<strong>{{ fee.net_value|money:event.currency }}</strong>
{% if fee.tax_rate %}
<br />
<small>
@@ -175,7 +176,7 @@
</small>
{% endif %}
{% else %}
<strong>{{ event.currency }} {{ fee.value|floatformat:2 }}</strong>
<strong>{{ fee.value|money:event.currency }}</strong>
{% if fee.tax_rate %}
<br />
<small>
@@ -195,7 +196,7 @@
<strong>{% trans "Net total" %}</strong>
</div>
<div class="col-md-3 col-xs-6 col-md-offset-5 price">
{{ event.currency }} {{ cart.net_total|floatformat:2 }}
{{ cart.net_total|money:event.currency }}
</div>
<div class="clearfix"></div>
</div>
@@ -204,7 +205,7 @@
<strong>{% trans "Taxes" %}</strong>
</div>
<div class="col-md-3 col-xs-6 col-md-offset-5 price">
{{ event.currency }} {{ cart.tax_total|floatformat:2 }}
{{ cart.tax_total|money:event.currency }}
</div>
<div class="clearfix"></div>
</div>
@@ -214,7 +215,7 @@
<strong>{% trans "Total" %}</strong>
</div>
<div class="col-md-3 col-xs-6 col-md-offset-5 price">
<strong>{{ event.currency }} {{ cart.total|floatformat:2 }}</strong>
<strong>{{ cart.total|money:event.currency }}</strong>
</div>
<div class="clearfix"></div>
</div>

View File

@@ -2,6 +2,7 @@
{% load i18n %}
{% load l10n %}
{% load eventurl %}
{% load money %}
{% load thumbnail %}
{% load eventsignal %}
{% load rich_text %}
@@ -250,13 +251,13 @@
</div>
<div class="col-md-2 col-xs-6 price">
{% if item.min_price != item.max_price or item.free_price %}
{% blocktrans trimmed with minprice=item.min_price|floatformat:2 currency=event.currency %}
from {{ currency }} {{ minprice }}
{% blocktrans trimmed with minprice=item.min_price|money:event.currency %}
from {{ minprice }}
{% endblocktrans %}
{% elif not item.min_price and not item.max_price %}
{% trans "FREE" context "price" %}
{% else %}
{{ event.currency }} {{ item.min_price|floatformat:2 }}
{{ item.min_price|money:event.currency }}
{% endif %}
</div>
<div class="col-md-2 col-xs-6 availability-box">
@@ -288,18 +289,18 @@
<span class="input-group-addon">{{ event.currency }}</span>
<input type="number" class="form-control input-item-price"
placeholder="0"
min="{% if event.settings.display_net_prices %}{{ var.display_price.net|stringformat:"0.2f" }}{% else %}{{ var.display_price.gross|stringformat:"0.2f" }}{% endif %}"
min="{% if event.settings.display_net_prices %}{{ var.display_price.net|money_numberfield:event.currency }}{% else %}{{ var.display_price.gross|money_numberfield:event.currency }}{% endif %}"
name="price_{{ item.id }}_{{ var.id }}"
step="any"
value="{% if event.settings.display_net_prices %}{{var.display_price.net|stringformat:"0.2f" }}{% else %}{{ var.display_price.gross|stringformat:"0.2f" }}{% endif %}"
value="{% if event.settings.display_net_prices %}{{var.display_price.net|money_numberfield:event.currency }}{% else %}{{ var.display_price.gross|money_numberfield:event.currency }}{% endif %}"
>
</div>
{% elif not var.display_price.gross %}
{% trans "FREE" context "price" %}
{% elif event.settings.display_net_prices %}
{{ event.currency }} {{ var.display_price.net|floatformat:2 }}
{{ var.display_price.net|money:event.currency }}
{% else %}
{{ event.currency }} {{ var.display_price.gross|floatformat:2 }}
{{ var.display_price.gross|money:event.currency }}
{% endif %}
{% 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 %}
@@ -377,17 +378,17 @@
<div class="input-group input-group-price">
<span class="input-group-addon">{{ event.currency }}</span>
<input type="number" class="form-control input-item-price" placeholder="0"
min="{% if event.settings.display_net_prices %}{{ item.display_price.net|stringformat:"0.2f" }}{% else %}{{ item.display_price.gross|stringformat:"0.2f" }}{% endif %}"
min="{% if event.settings.display_net_prices %}{{ item.display_price.net|money_numberfield:event.currency }}{% else %}{{ item.display_price.gross|money_numberfield:event.currency }}{% endif %}"
name="price_{{ item.id }}"
value="{% if event.settings.display_net_prices %}{{item.display_price.net|stringformat:"0.2f" }}{% else %}{{ item.display_price.gross|stringformat:"0.2f" }}{% endif %}"
value="{% if event.settings.display_net_prices %}{{item.display_price.net|money_numberfield:event.currency }}{% else %}{{ item.display_price.gross|money_numberfield:event.currency }}{% endif %}"
step="any">
</div>
{% elif not item.display_price.gross %}
{% trans "FREE" context "price" %}
{% elif event.settings.display_net_prices %}
{{ event.currency }} {{ item.display_price.net|floatformat:2 }}
{{ item.display_price.net|money:event.currency }}
{% else %}
{{ event.currency }} {{ item.display_price.gross|floatformat:2 }}
{{ item.display_price.gross|money:event.currency }}
{% endif %}
{% 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 %}

View File

@@ -1,6 +1,7 @@
{% extends "pretixpresale/event/base.html" %}
{% load i18n %}
{% load eventurl %}
{% load money %}
{% block title %}{% trans "Change payment method" %}{% endblock %}
{% block content %}
<h2>
@@ -22,7 +23,7 @@
<div class="panel-heading">
<h4 class="panel-title">
<label class="radio">
<strong class="pull-right">{% if p.fee_diff >= 0 %}+{% else %}-{% endif %} {{ p.fee_diff_abs|floatformat:2 }} {{ event.currency }}</strong>
<strong class="pull-right">{% if p.fee_diff >= 0 %}+{% else %}-{% endif %} {{ p.fee_diff_abs|money:event.currency }}</strong>
<input type="radio" name="payment" value="{{ p.provider.identifier }}"
data-parent="#payment_accordion"
{% if selected == p.provider.identifier %}checked="checked"{% endif %}

View File

@@ -1,6 +1,7 @@
{% extends "pretixpresale/event/base.html" %}
{% load i18n %}
{% load eventurl %}
{% load money %}
{% block title %}{% trans "Pay order" %}{% endblock %}
{% block content %}
<h2>
@@ -18,8 +19,8 @@
<div class="panel-heading">
<div class="pull-right">
<strong>
{% blocktrans trimmed with total=order.total|floatformat:2 currency=request.event.currency %}
Total: {{ total }} {{ currency }}
{% blocktrans trimmed with total=order.total|money:request.event.currency %}
Total: {{ total }}
{% endblocktrans %}
</strong>
</div>

View File

@@ -1,6 +1,7 @@
{% extends "pretixpresale/event/base.html" %}
{% load i18n %}
{% load l10n %}
{% load money %}
{% load eventurl %}
{% load eventsignal %}
{% load thumbnail %}
@@ -56,13 +57,13 @@
</div>
<div class="col-md-2 col-xs-6 price">
{% if item.min_price != item.max_price or item.free_price %}
{% blocktrans trimmed with minprice=item.min_price|floatformat:2 currency=event.currency %}
from {{ currency }} {{ minprice }}
{% blocktrans trimmed with minprice=item.min_price|money:event.currency %}
from {{ minprice }}
{% endblocktrans %}
{% elif not item.min_price and not item.max_price %}
{% trans "FREE" context "price" %}
{% else %}
{{ event.currency }} {{ item.min_price|floatformat:2 }}
{{ item.min_price|money:event.currency }}
{% endif %}
</div>
<div class="col-md-2 col-xs-6 availability-box">
@@ -94,9 +95,9 @@
{% elif not var.display_price.gross %}
{% trans "FREE" context "price" %}
{% elif event.settings.display_net_prices %}
{{ event.currency }} {{ var.display_price.net|floatformat:2 }}
{{ var.display_price.net|money:event.currency }}
{% else %}
{{ event.currency }} {{ var.display_price.gross|floatformat:2 }}
{{ var.display_price.gross|money:event.currency }}
{% endif %}
{% 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 %}
@@ -164,9 +165,9 @@
{% elif not item.display_price.gross %}
{% trans "FREE" context "price" %}
{% elif event.settings.display_net_prices %}
{{ event.currency }} {{ item.display_price.net|floatformat:2 }}
{{ item.display_price.net|money:event.currency }}
{% else %}
{{ event.currency }} {{ item.display_price.gross|floatformat:2 }}
{{ item.display_price.gross|money:event.currency }}
{% endif %}
{% 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 %}

View File

@@ -103,6 +103,24 @@ FETCH_ECB_RATES = config.getboolean('pretix', 'ecb_rates', fallback=True)
DEFAULT_CURRENCY = config.get('pretix', 'currency', fallback='EUR')
CURRENCIES = list(currencies)
CURRENCY_PLACES = {
# default is 2
'BIF': 0,
'CLP': 0,
'DJF': 0,
'GNF': 0,
'JPY': 0,
'KMF': 0,
'KRW': 0,
'MGA': 0,
'PYG': 0,
'RWF': 0,
'VND': 0,
'VUV': 0,
'XAF': 0,
'XOF': 0,
'XPF': 0,
}
ALLOWED_HOSTS = ['*']

View File

@@ -30,6 +30,7 @@ django-markup
markdown
bleach==2.*
raven
babel
django-i18nfield>=1.2.1
django-hijack==2.1.*
# Stripe

View File

@@ -95,6 +95,7 @@ setup(
'markdown',
'bleach==2.*',
'raven',
'babel',
'paypalrestsdk==1.12.*',
'pycparser==2.13',
'django-redis==4.7.*',

View File

@@ -77,6 +77,36 @@ def test_perform_success(env, factory, monkeypatch):
assert order.status == Order.STATUS_PAID
@pytest.mark.django_db
def test_perform_success_zero_decimal_currency(env, factory, monkeypatch):
event, order = env
event.currency = 'JPY'
event.save()
def charge_create(**kwargs):
assert kwargs['amount'] == 13
assert kwargs['currency'] == 'jpy'
assert kwargs['source'] == 'tok_189fTT2eZvKYlo2CvJKzEzeu'
c = MockedCharge()
c.status = 'succeeded'
c.paid = True
return c
monkeypatch.setattr("stripe.Charge.create", charge_create)
prov = StripeCC(event)
req = factory.post('/', {
'stripe_token': 'tok_189fTT2eZvKYlo2CvJKzEzeu',
'stripe_last4': '4242',
'stripe_brand': 'Visa'
})
req.session = {}
prov.checkout_prepare(req, {})
assert 'payment_stripe_token' in req.session
prov.payment_perform(req, order)
order.refresh_from_db()
assert order.status == Order.STATUS_PAID
@pytest.mark.django_db
def test_perform_card_error(env, factory, monkeypatch):
event, order = env

View File

@@ -1306,7 +1306,7 @@ class CheckoutTestCase(TestCase):
target_status_code=200)
response = self.client.get('/%s/%s/checkout/addons/' % (self.orga.slug, self.event.slug))
assert 'Workshop 1' in response.rendered_content
assert 'EUR 12.00' in response.rendered_content
assert '12.00' in response.rendered_content
def test_set_addons_included(self):
ItemAddOn.objects.create(base_item=self.ticket, addon_category=self.workshopcat, min_count=1,
@@ -1320,7 +1320,7 @@ class CheckoutTestCase(TestCase):
self.assertRedirects(response, '/%s/%s/checkout/addons/' % (self.orga.slug, self.event.slug),
target_status_code=200)
assert 'Workshop 1' in response.rendered_content
assert 'EUR 12.00' not in response.rendered_content
assert '12.00' not in response.rendered_content
def test_set_addons_subevent(self):
self.event.has_subevents = True
@@ -1340,7 +1340,7 @@ class CheckoutTestCase(TestCase):
response = self.client.get('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), follow=True)
self.assertRedirects(response, '/%s/%s/checkout/addons/' % (self.orga.slug, self.event.slug),
target_status_code=200)
assert 'Workshop 1 (+ EUR 42.00)' in response.rendered_content
assert 'Workshop 1 (+ 42.00)' in response.rendered_content
def test_set_addons_subevent_net_prices(self):
self.event.has_subevents = True
@@ -1365,8 +1365,8 @@ class CheckoutTestCase(TestCase):
response = self.client.get('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), follow=True)
self.assertRedirects(response, '/%s/%s/checkout/addons/' % (self.orga.slug, self.event.slug),
target_status_code=200)
assert 'Workshop 1 (+ EUR 35.29 plus 19.00% VAT)' in response.rendered_content
assert 'A (+ EUR 10.08 plus 19.00% VAT)' in response.rendered_content
assert 'Workshop 1 (+ 35.29 plus 19.00% VAT)' in response.rendered_content
assert 'A (+ 10.08 plus 19.00% VAT)' in response.rendered_content
def test_confirm_subevent_presale_not_yet(self):
self.event.has_subevents = True

View File

@@ -409,7 +409,7 @@ class OrdersTest(TestCase):
'/%s/%s/order/%s/%s/pay/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret),
)
assert 'Test dummy' in response.rendered_content
assert '+ 12.00' in response.rendered_content
assert '+ 12.00' in response.rendered_content
response = self.client.post(
'/%s/%s/order/%s/%s/pay/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret),
{