From a7f9e100d271f5a551135c1eb9b8eded482bb323 Mon Sep 17 00:00:00 2001 From: Richard Schreiber Date: Mon, 30 Jan 2023 17:24:09 +0100 Subject: [PATCH] Clean up localization or error messages in cart (#3049) --- src/pretix/base/services/cart.py | 199 ++++++++++++++++----------- src/pretix/base/services/orders.py | 210 ++++++++++++++++------------- src/pretix/presale/views/cart.py | 6 +- src/pretix/presale/views/order.py | 14 +- src/tests/presale/test_cart.py | 2 +- 5 files changed, 245 insertions(+), 186 deletions(-) diff --git a/src/pretix/base/services/cart.py b/src/pretix/base/services/cart.py index 3f7cb2c493..13f5b36085 100644 --- a/src/pretix/base/services/cart.py +++ b/src/pretix/base/services/cart.py @@ -43,7 +43,9 @@ from django.db import DatabaseError, transaction from django.db.models import Count, Exists, IntegerField, OuterRef, Q, Value from django.dispatch import receiver from django.utils.timezone import make_aware, now -from django.utils.translation import gettext as _, pgettext_lazy +from django.utils.translation import ( + gettext as _, gettext_lazy, ngettext_lazy, pgettext_lazy, +) from django_scopes import scopes_disabled from pretix.base.channels import get_all_sales_channels @@ -81,74 +83,119 @@ class CartError(Exception): if msgargs: msg = _(msg) % msgargs else: - msg = _(msg) + # force msg to string to make sure lazy-translation is done in current locale-context + # otherwise translation might happen in celery-context, which uses default-locale + # also translate with _/gettext to keep it backwards compatible + msg = _(str(msg)) super().__init__(msg) error_messages = { - 'busy': _('We were not able to process your request completely as the ' - 'server was too busy. Please try again.'), - 'empty': _('You did not select any products.'), - 'unknown_position': _('Unknown cart position.'), + 'busy': gettext_lazy( + 'We were not able to process your request completely as the ' + 'server was too busy. Please try again.' + ), + 'empty': gettext_lazy('You did not select any products.'), + 'unknown_position': gettext_lazy('Unknown cart position.'), 'subevent_required': pgettext_lazy('subevent', 'No date was specified.'), - 'not_for_sale': _('You selected a product which is not available for sale.'), - 'unavailable': _('Some of the products you selected are no longer available. ' - 'Please see below for details.'), - 'in_part': _('Some of the products you selected are no longer available in ' - 'the quantity you selected. Please see below for details.'), - 'max_items': _("You cannot select more than %s items per order."), - 'max_items_per_product': _("You cannot select more than %(max)s items of the product %(product)s."), - 'min_items_per_product': _("You need to select at least %(min)s items of the product %(product)s."), - 'min_items_per_product_removed': _("We removed %(product)s from your cart as you can not buy less than " - "%(min)s items of it."), - 'not_started': _('The booking period for this event has not yet started.'), - 'ended': _('The booking period for this event has ended.'), - 'payment_ended': _('All payments for this event need to be confirmed already, so no new orders can be created.'), - 'some_subevent_not_started': _('The booking period for this event has not yet started. The affected positions ' - 'have been removed from your cart.'), - 'some_subevent_ended': _('The booking period for one of the events in your cart has ended. The affected ' - 'positions have been removed from your cart.'), - 'price_too_high': _('The entered price is to high.'), - 'voucher_invalid': _('This voucher code is not known in our database.'), - 'voucher_min_usages': _('The voucher code "%(voucher)s" can only be used if you select at least %(number)s ' - 'matching products.'), - 'voucher_min_usages_removed': _('The voucher code "%(voucher)s" can only be used if you select at least ' - '%(number)s matching products. We have therefore removed some positions from ' - 'your cart that can no longer be purchased like this.'), - 'voucher_redeemed': _('This voucher code has already been used the maximum number of times allowed.'), - 'voucher_redeemed_cart': _('This voucher code is currently locked since it is already contained in a cart. This ' - 'might mean that someone else is redeeming this voucher right now, or that you tried ' - 'to redeem it before but did not complete the checkout process. You can try to use it ' - 'again in %d minutes.'), - 'voucher_redeemed_partial': _('This voucher code can only be redeemed %d more times.'), - 'voucher_double': _('You already used this voucher code. Remove the associated line from your ' - 'cart if you want to use it for a different product.'), - 'voucher_expired': _('This voucher is expired.'), - 'voucher_invalid_item': _('This voucher is not valid for this product.'), - 'voucher_invalid_seat': _('This voucher is not valid for this seat.'), - 'voucher_no_match': _('We did not find any position in your cart that we could use this voucher for. If you want ' - 'to add something new to your cart using that voucher, you can do so with the voucher ' - 'redemption option on the bottom of the page.'), - 'voucher_item_not_available': _( + 'not_for_sale': gettext_lazy('You selected a product which is not available for sale.'), + 'unavailable': gettext_lazy( + 'Some of the products you selected are no longer available. ' + 'Please see below for details.' + ), + 'in_part': gettext_lazy( + 'Some of the products you selected are no longer available in ' + 'the quantity you selected. Please see below for details.' + ), + 'max_items': ngettext_lazy( + "You cannot select more than %s item per order.", + "You cannot select more than %s items per order." + ), + 'max_items_per_product': ngettext_lazy( + "You cannot select more than %(max)s item of the product %(product)s.", + "You cannot select more than %(max)s items of the product %(product)s.", + "max" + ), + 'min_items_per_product': ngettext_lazy( + "You need to select at least %(min)s item of the product %(product)s.", + "You need to select at least %(min)s items of the product %(product)s.", + "min" + ), + 'min_items_per_product_removed': ngettext_lazy( + "We removed %(product)s from your cart as you can not buy less than %(min)s item of it.", + "We removed %(product)s from your cart as you can not buy less than %(min)s items of it.", + "min" + ), + 'not_started': gettext_lazy('The booking period for this event has not yet started.'), + 'ended': gettext_lazy('The booking period for this event has ended.'), + 'payment_ended': gettext_lazy('All payments for this event need to be confirmed already, so no new orders can be created.'), + 'some_subevent_not_started': gettext_lazy( + 'The booking period for this event has not yet started. The affected positions ' + 'have been removed from your cart.'), + 'some_subevent_ended': gettext_lazy( + 'The booking period for one of the events in your cart has ended. The affected ' + 'positions have been removed from your cart.'), + 'price_too_high': gettext_lazy('The entered price is to high.'), + 'voucher_invalid': gettext_lazy('This voucher code is not known in our database.'), + 'voucher_min_usages': gettext_lazy( + 'The voucher code "%(voucher)s" can only be used if you select at least %(number)s ' + 'matching products.' + ), + 'voucher_min_usages_removed': ngettext_lazy( + 'The voucher code "%(voucher)s" can only be used if you select at least %(number)s matching products. ' + 'We have therefore removed some positions from your cart that can no longer be purchased like this.', + 'The voucher code "%(voucher)s" can only be used if you select at least %(number)s matching products. ' + 'We have therefore removed some positions from your cart that can no longer be purchased like this.', + 'number' + ), + 'voucher_redeemed': gettext_lazy('This voucher code has already been used the maximum number of times allowed.'), + 'voucher_redeemed_cart': gettext_lazy( + 'This voucher code is currently locked since it is already contained in a cart. This ' + 'might mean that someone else is redeeming this voucher right now, or that you tried ' + 'to redeem it before but did not complete the checkout process. You can try to use it ' + 'again in %d minutes.' + ), + 'voucher_redeemed_partial': gettext_lazy('This voucher code can only be redeemed %d more times.'), + 'voucher_whole_cart_not_combined': gettext_lazy('Applying a voucher to the whole cart should not be combined with other operations.'), + 'voucher_double': gettext_lazy( + 'You already used this voucher code. Remove the associated line from your ' + 'cart if you want to use it for a different product.' + ), + 'voucher_expired': gettext_lazy('This voucher is expired.'), + 'voucher_invalid_item': gettext_lazy('This voucher is not valid for this product.'), + 'voucher_invalid_seat': gettext_lazy('This voucher is not valid for this seat.'), + 'voucher_no_match': gettext_lazy( + 'We did not find any position in your cart that we could use this voucher for. If you want ' + 'to add something new to your cart using that voucher, you can do so with the voucher ' + 'redemption option on the bottom of the page.' + ), + 'voucher_item_not_available': gettext_lazy( 'Your voucher is valid for a product that is currently not for sale.'), 'voucher_invalid_subevent': pgettext_lazy('subevent', 'This voucher is not valid for this event date.'), - 'voucher_required': _('You need a valid voucher code to order this product.'), + 'voucher_required': gettext_lazy('You need a valid voucher code to order this product.'), 'inactive_subevent': pgettext_lazy('subevent', 'The selected event date is not active.'), - 'addon_invalid_base': _('You can not select an add-on for the selected product.'), - 'addon_duplicate_item': _('You can not select two variations of the same add-on product.'), - 'addon_max_count': _('You can select at most %(max)s add-ons from the category %(cat)s for the product %(base)s.'), - 'addon_min_count': _('You need to select at least %(min)s add-ons from the category %(cat)s for the ' - 'product %(base)s.'), - 'addon_no_multi': _('You can select every add-ons from the category %(cat)s for the product %(base)s at most once.'), - 'addon_only': _('One of the products you selected can only be bought as an add-on to another product.'), - 'bundled_only': _('One of the products you selected can only be bought part of a bundle.'), - 'seat_required': _('You need to select a specific seat.'), - 'seat_invalid': _('Please select a valid seat.'), - 'seat_forbidden': _('You can not select a seat for this position.'), - 'seat_unavailable': _('The seat you selected has already been taken. Please select a different seat.'), - 'seat_multiple': _('You can not select the same seat multiple times.'), - 'gift_card': _("You entered a gift card instead of a voucher. Gift cards can be entered later on when you're asked for your payment details."), - 'country_blocked': _('One of the selected products is not available in the selected country.'), + 'addon_invalid_base': gettext_lazy('You can not select an add-on for the selected product.'), + 'addon_duplicate_item': gettext_lazy('You can not select two variations of the same add-on product.'), + 'addon_max_count': ngettext_lazy( + 'You can select at most %(max)s add-on from the category %(cat)s for the product %(base)s.', + 'You can select at most %(max)s add-ons from the category %(cat)s for the product %(base)s.', + 'max' + ), + 'addon_min_count': ngettext_lazy( + 'You need to select at least %(min)s add-on from the category %(cat)s for the product %(base)s.', + 'You need to select at least %(min)s add-ons from the category %(cat)s for the product %(base)s.', + 'min' + ), + 'addon_no_multi': gettext_lazy('You can select every add-ons from the category %(cat)s for the product %(base)s at most once.'), + 'addon_only': gettext_lazy('One of the products you selected can only be bought as an add-on to another product.'), + 'bundled_only': gettext_lazy('One of the products you selected can only be bought part of a bundle.'), + 'seat_required': gettext_lazy('You need to select a specific seat.'), + 'seat_invalid': gettext_lazy('Please select a valid seat.'), + 'seat_forbidden': gettext_lazy('You can not select a seat for this position.'), + 'seat_unavailable': gettext_lazy('The seat you selected has already been taken. Please select a different seat.'), + 'seat_multiple': gettext_lazy('You can not select the same seat multiple times.'), + 'gift_card': gettext_lazy("You entered a gift card instead of a voucher. Gift cards can be entered later on when you're asked for your payment details."), + 'country_blocked': gettext_lazy('One of the selected products is not available in the selected country.'), } @@ -320,8 +367,7 @@ class CartManager: cartsize -= len([1 for op in self._operations if isinstance(op, self.RemoveOperation) if not op.position.addon_to_id]) if cartsize > int(self.event.settings.max_items_per_order): - # TODO: i18n plurals - raise CartError(_(error_messages['max_items']) % (self.event.settings.max_items_per_order,)) + raise CartError(error_messages['max_items'] % self.event.settings.max_items_per_order) def _check_item_constraints(self, op, current_ops=[]): if isinstance(op, (self.AddOperation, self.ExtendOperation)): @@ -494,7 +540,7 @@ class CartManager: def apply_voucher(self, voucher_code: str): if self._operations: - raise CartError('Applying a voucher to the whole cart should not be combined with other operations.') + raise CartError(error_messages['voucher_whole_cart_not_combined']) try: voucher = self.event.vouchers.get(code__iexact=voucher_code.strip()) except Voucher.DoesNotExist: @@ -539,7 +585,7 @@ class CartManager: for voucher, cnt in list(voucher_use_diff.items()): if 0 < cnt < voucher.min_usages_remaining: raise CartError( - _(error_messages['voucher_min_usages']) % { + error_messages['voucher_min_usages'] % { 'voucher': voucher.code, 'number': voucher.min_usages_remaining, } @@ -843,22 +889,16 @@ class CartManager: for (i, v), c in selected.items(): n_per_i[i] += c if sum(selected.values()) > iao.max_count: - # TODO: Proper i18n - # TODO: Proper pluralization raise CartError( - error_messages['addon_max_count'], - { + error_messages['addon_max_count'] % { 'base': str(item.name), 'max': iao.max_count, 'cat': str(iao.addon_category.name), } ) elif sum(selected.values()) < iao.min_count: - # TODO: Proper i18n - # TODO: Proper pluralization raise CartError( - error_messages['addon_min_count'], - { + error_messages['addon_min_count'] % { 'base': str(item.name), 'min': iao.min_count, 'cat': str(iao.addon_category.name), @@ -866,8 +906,7 @@ class CartManager: ) elif any(v > 1 for v in n_per_i.values()) and not iao.multi_allowed: raise CartError( - error_messages['addon_no_multi'], - { + error_messages['addon_no_multi'] % { 'base': str(item.name), 'cat': str(iao.addon_category.name), } @@ -931,7 +970,7 @@ class CartManager: if item.max_per_order and count > item.max_per_order: raise CartError( - _(error_messages['max_items_per_product']) % { + error_messages['max_items_per_product'] % { 'max': item.max_per_order, 'product': item.name } @@ -945,13 +984,13 @@ class CartManager: for p in self.positions: if p.item_id == item.pk and p.pk not in removals: self._operations.append(self.RemoveOperation(position=p)) - err = _(error_messages['min_items_per_product_removed']) % { + err = error_messages['min_items_per_product_removed'] % { 'min': item.min_per_order, 'product': item.name } if not err: raise CartError( - _(error_messages['min_items_per_product']) % { + error_messages['min_items_per_product'] % { 'min': item.min_per_order, 'product': item.name } @@ -980,13 +1019,13 @@ class CartManager: for p in self.positions: if p.voucher_id == voucher.pk and p.pk not in removals: self._operations.append(self.RemoveOperation(position=p)) - err = _(error_messages['voucher_min_usages_removed']) % { + err = error_messages['voucher_min_usages_removed'] % { 'voucher': voucher.code, 'number': voucher.min_usages_remaining, } if not err: raise CartError( - _(error_messages['voucher_min_usages']) % { + error_messages['voucher_min_usages'] % { 'voucher': voucher.code, 'number': voucher.min_usages_remaining, } diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index 2ba9494595..9f9c1fed8c 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -54,15 +54,13 @@ from django.db.transaction import get_connection from django.dispatch import receiver from django.utils.functional import cached_property from django.utils.timezone import make_aware, now -from django.utils.translation import gettext as _ +from django.utils.translation import gettext as _, gettext_lazy, ngettext_lazy from django_scopes import scopes_disabled from pretix.api.models import OAuthApplication from pretix.base.channels import get_all_sales_channels from pretix.base.email import get_email_context -from pretix.base.i18n import ( - LazyLocaleException, get_language_without_region, language, -) +from pretix.base.i18n import get_language_without_region, language from pretix.base.models import ( CartPosition, Device, Event, GiftCard, Item, ItemVariation, Membership, Order, OrderPayment, OrderPosition, Quota, Seat, SeatCategoryMapping, User, @@ -101,47 +99,94 @@ from pretix.helpers import OF_SELF from pretix.helpers.models import modelcopy from pretix.helpers.periodic import minimum_interval + +class OrderError(Exception): + def __init__(self, *args): + msg = args[0] + msgargs = args[1] if len(args) > 1 else None + self.args = args + if msgargs: + msg = _(msg) % msgargs + else: + # force msg to string to make sure lazy-translation is done in current locale-context + # otherwise translation might happen in celery-context, which uses default-locale + # also translate with _/gettext to keep it backwards compatible + msg = _(str(msg)) + super().__init__(msg) + + error_messages = { - 'unavailable': _('Some of the products you selected were no longer available. ' - 'Please see below for details.'), - 'in_part': _('Some of the products you selected were no longer available in ' - 'the quantity you selected. Please see below for details.'), - 'price_changed': _('The price of some of the items in your cart has changed in the ' - 'meantime. Please see below for details.'), - 'internal': _("An internal error occurred, please try again."), - 'empty': _("Your cart is empty."), - 'max_items_per_product': _("You cannot select more than %(max)s items of the product %(product)s. We removed the " - "surplus items from your cart."), - 'busy': _('We were not able to process your request completely as the ' - 'server was too busy. Please try again.'), - 'not_started': _('The booking period for this event has not yet started.'), - 'ended': _('The booking period has ended.'), - 'voucher_min_usages': _('The voucher code "%(voucher)s" can only be used if you select at least %(number)s ' - 'matching products.'), - 'voucher_invalid': _('The voucher code used for one of the items in your cart is not known in our database.'), - 'voucher_redeemed': _('The voucher code used for one of the items in your cart has already been used the maximum ' - 'number of times allowed. We removed this item from your cart.'), - 'voucher_budget_used': _('The voucher code used for one of the items in your cart has already been too often. We ' - 'adjusted the price of the item in your cart.'), - 'voucher_expired': _('The voucher code used for one of the items in your cart is expired. We removed this item ' - 'from your cart.'), - 'voucher_invalid_item': _('The voucher code used for one of the items in your cart is not valid for this item. We ' - 'removed this item from your cart.'), - 'voucher_required': _('You need a valid voucher code to order one of the products.'), - 'some_subevent_not_started': _('The booking period for one of the events in your cart has not yet started. The ' - 'affected positions have been removed from your cart.'), - 'some_subevent_ended': _('The booking period for one of the events in your cart has ended. The affected ' - 'positions have been removed from your cart.'), - 'seat_invalid': _('One of the seats in your order was invalid, we removed the position from your cart.'), - 'seat_unavailable': _('One of the seats in your order has been taken in the meantime, we removed the position from your cart.'), - 'country_blocked': _('One of the selected products is not available in the selected country.'), - 'not_for_sale': _('You selected a product which is not available for sale.'), - 'addon_invalid_base': _('You can not select an add-on for the selected product.'), - 'addon_duplicate_item': _('You can not select two variations of the same add-on product.'), - 'addon_max_count': _('You can select at most %(max)s add-ons from the category %(cat)s for the product %(base)s.'), - 'addon_min_count': _('You need to select at least %(min)s add-ons from the category %(cat)s for the ' - 'product %(base)s.'), - 'addon_no_multi': _('You can select every add-ons from the category %(cat)s for the product %(base)s at most once.'), + 'unavailable': gettext_lazy( + 'Some of the products you selected were no longer available. ' + 'Please see below for details.' + ), + 'in_part': gettext_lazy( + 'Some of the products you selected were no longer available in ' + 'the quantity you selected. Please see below for details.' + ), + 'price_changed': gettext_lazy( + 'The price of some of the items in your cart has changed in the ' + 'meantime. Please see below for details.' + ), + 'internal': gettext_lazy("An internal error occurred, please try again."), + 'empty': gettext_lazy("Your cart is empty."), + 'max_items_per_product': ngettext_lazy( + "You cannot select more than %(max)s item of the product %(product)s. We removed the surplus items from your cart.", + "You cannot select more than %(max)s items of the product %(product)s. We removed the surplus items from your cart.", + "max" + ), + 'busy': gettext_lazy( + 'We were not able to process your request completely as the ' + 'server was too busy. Please try again.' + ), + 'not_started': gettext_lazy('The booking period for this event has not yet started.'), + 'ended': gettext_lazy('The booking period has ended.'), + 'voucher_min_usages': ngettext_lazy( + 'The voucher code "%(voucher)s" can only be used if you select at least %(number)s matching products.', + 'The voucher code "%(voucher)s" can only be used if you select at least %(number)s matching products.', + 'number' + ), + 'voucher_invalid': gettext_lazy('The voucher code used for one of the items in your cart is not known in our database.'), + 'voucher_redeemed': gettext_lazy( + 'The voucher code used for one of the items in your cart has already been used the maximum ' + 'number of times allowed. We removed this item from your cart.' + ), + 'voucher_budget_used': gettext_lazy( + 'The voucher code used for one of the items in your cart has already been too often. We ' + 'adjusted the price of the item in your cart.' + ), + 'voucher_expired': gettext_lazy( + 'The voucher code used for one of the items in your cart is expired. We removed this item from your cart.' + ), + 'voucher_invalid_item': gettext_lazy( + 'The voucher code used for one of the items in your cart is not valid for this item. We removed this item from your cart.' + ), + 'voucher_required': gettext_lazy('You need a valid voucher code to order one of the products.'), + 'some_subevent_not_started': gettext_lazy( + 'The booking period for one of the events in your cart has not yet started. The ' + 'affected positions have been removed from your cart.' + ), + 'some_subevent_ended': gettext_lazy( + 'The booking period for one of the events in your cart has ended. The affected ' + 'positions have been removed from your cart.' + ), + 'seat_invalid': gettext_lazy('One of the seats in your order was invalid, we removed the position from your cart.'), + 'seat_unavailable': gettext_lazy('One of the seats in your order has been taken in the meantime, we removed the position from your cart.'), + 'country_blocked': gettext_lazy('One of the selected products is not available in the selected country.'), + 'not_for_sale': gettext_lazy('You selected a product which is not available for sale.'), + 'addon_invalid_base': gettext_lazy('You can not select an add-on for the selected product.'), + 'addon_duplicate_item': gettext_lazy('You can not select two variations of the same add-on product.'), + 'addon_max_count': ngettext_lazy( + 'You can select at most %(max)s add-on from the category %(cat)s for the product %(base)s.', + 'You can select at most %(max)s add-ons from the category %(cat)s for the product %(base)s.', + 'max' + ), + 'addon_min_count': ngettext_lazy( + 'You need to select at least %(min)s add-on from the category %(cat)s for the product %(base)s.', + 'You need to select at least %(min)s add-ons from the category %(cat)s for the product %(base)s.', + 'min' + ), + 'addon_no_multi': gettext_lazy('You can select every add-ons from the category %(cat)s for the product %(base)s at most once.'), } logger = logging.getLogger(__name__) @@ -157,7 +202,7 @@ def reactivate_order(order: Order, force: bool=False, user: User=None, auth=None enough quota. """ if order.status != Order.STATUS_CANCELED: - raise OrderError('The order was not canceled.') + raise OrderError(_('The order was not canceled.')) with order.event.lock() as now_dt: is_available = order._is_still_available(now_dt, count_waitinglist=False, check_voucher_usage=True, @@ -538,18 +583,6 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device return order.pk -class OrderError(LazyLocaleException): - def __init__(self, *args): - msg = args[0] - msgargs = args[1] if len(args) > 1 else None - self.args = args - if msgargs: - msg = _(msg) % msgargs - else: - msg = _(msg) - super().__init__(msg) - - def _check_date(event: Event, now_dt: datetime): if event.presale_start and now_dt < event.presale_start: raise OrderError(error_messages['not_started']) @@ -570,7 +603,6 @@ def _check_date(event: Event, now_dt: datetime): def _check_positions(event: Event, now_dt: datetime, positions: List[CartPosition], address: InvoiceAddress=None, sales_channel='web', customer=None): err = None - errargs = None _check_date(event, now_dt) products_seen = Counter() @@ -607,9 +639,10 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio products_seen[cp.item] += 1 if cp.item.max_per_order and products_seen[cp.item] > cp.item.max_per_order: - err = error_messages['max_items_per_product'] - errargs = {'max': cp.item.max_per_order, - 'product': cp.item.name} + err = error_messages['max_items_per_product'] % { + 'max': cp.item.max_per_order, + 'product': cp.item.name + } delete(cp) break @@ -728,7 +761,7 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio for voucher, cnt in v_usages.items(): if 0 < cnt < voucher.min_usages_remaining: - raise OrderError(error_messages['voucher_min_usages'], { + raise OrderError(error_messages['voucher_min_usages'] % { 'voucher': voucher.code, 'number': voucher.min_usages_remaining, }) @@ -791,7 +824,7 @@ def _check_positions(event: Event, now_dt: datetime, positions: List[CartPositio cp.save() if err: - raise OrderError(err, errargs) + raise OrderError(err) def _get_fees(positions: List[CartPosition], payment_requests: List[dict], address: InvoiceAddress, @@ -1344,22 +1377,26 @@ def notify_user_changed_order(order, user=None, auth=None, invoices=[]): class OrderChangeManager: error_messages = { - 'product_without_variation': _('You need to select a variation of the product.'), - 'quota': _('The quota {name} does not have enough capacity left to perform the operation.'), - 'quota_missing': _('There is no quota defined that allows this operation.'), - 'product_invalid': _('The selected product is not active or has no price set.'), - 'complete_cancel': _('This operation would leave the order empty. Please cancel the order itself instead.'), - 'paid_to_free_exceeded': _('This operation would make the order free and therefore immediately paid, however ' - 'no quota is available.'), - 'addon_to_required': _('This is an add-on product, please select the base position it should be added to.'), - 'addon_invalid': _('The selected base position does not allow you to add this product as an add-on.'), - 'subevent_required': _('You need to choose a subevent for the new position.'), - 'seat_unavailable': _('The selected seat "{seat}" is not available.'), - 'seat_subevent_mismatch': _('You selected seat "{seat}" for a date that does not match the selected ticket date. Please choose a seat again.'), - 'seat_required': _('The selected product requires you to select a seat.'), - 'seat_forbidden': _('The selected product does not allow to select a seat.'), - 'tax_rule_country_blocked': _('The selected country is blocked by your tax rule.'), - 'gift_card_change': _('You cannot change the price of a position that has been used to issue a gift card.'), + 'product_without_variation': gettext_lazy('You need to select a variation of the product.'), + 'quota': gettext_lazy('The quota {name} does not have enough capacity left to perform the operation.'), + 'quota_missing': gettext_lazy('There is no quota defined that allows this operation.'), + 'product_invalid': gettext_lazy('The selected product is not active or has no price set.'), + 'complete_cancel': gettext_lazy('This operation would leave the order empty. Please cancel the order itself instead.'), + 'paid_to_free_exceeded': gettext_lazy( + 'This operation would make the order free and therefore immediately paid, however ' + 'no quota is available.' + ), + 'addon_to_required': gettext_lazy('This is an add-on product, please select the base position it should be added to.'), + 'addon_invalid': gettext_lazy('The selected base position does not allow you to add this product as an add-on.'), + 'subevent_required': gettext_lazy('You need to choose a subevent for the new position.'), + 'seat_unavailable': gettext_lazy('The selected seat "{seat}" is not available.'), + 'seat_subevent_mismatch': gettext_lazy( + 'You selected seat "{seat}" for a date that does not match the selected ticket date. Please choose a seat again.' + ), + 'seat_required': gettext_lazy('The selected product requires you to select a seat.'), + 'seat_forbidden': gettext_lazy('The selected product does not allow to select a seat.'), + 'tax_rule_country_blocked': gettext_lazy('The selected country is blocked by your tax rule.'), + 'gift_card_change': gettext_lazy('You cannot change the price of a position that has been used to issue a gift card.'), } ItemOperation = namedtuple('ItemOperation', ('position', 'item', 'variation')) SubeventOperation = namedtuple('SubeventOperation', ('position', 'subevent')) @@ -1766,22 +1803,16 @@ class OrderChangeManager: for (i, v), c in selected.items(): n_per_i[i] += c if sum(selected.values()) > iao.max_count: - # TODO: Proper i18n - # TODO: Proper pluralization raise OrderError( - error_messages['addon_max_count'], - { + error_messages['addon_max_count'] % { 'base': str(item.name), 'max': iao.max_count, 'cat': str(iao.addon_category.name), } ) elif sum(selected.values()) < iao.min_count: - # TODO: Proper i18n - # TODO: Proper pluralization raise OrderError( - error_messages['addon_min_count'], - { + error_messages['addon_min_count'] % { 'base': str(item.name), 'min': iao.min_count, 'cat': str(iao.addon_category.name), @@ -1789,8 +1820,7 @@ class OrderChangeManager: ) elif any(v > 1 for v in n_per_i.values()) and not iao.multi_allowed: raise OrderError( - error_messages['addon_no_multi'], - { + error_messages['addon_no_multi'] % { 'base': str(item.name), 'cat': str(iao.addon_category.name), } @@ -2471,7 +2501,7 @@ def perform_order(self, event: Event, payments: List[dict], positions: List[str] except LockTimeoutException: self.retry() except (MaxRetriesExceededError, LockTimeoutException): - raise OrderError(str(error_messages['busy'])) + raise OrderError(error_messages['busy']) _unset = object() diff --git a/src/pretix/presale/views/cart.py b/src/pretix/presale/views/cart.py index 3f287b7f02..e2e529cafb 100644 --- a/src/pretix/presale/views/cart.py +++ b/src/pretix/presale/views/cart.py @@ -525,7 +525,7 @@ class CartAdd(EventViewMixin, CartActionMixin, AsyncAction, View): return JsonResponse({ 'redirect': self.get_error_url(), 'success': False, - 'message': _(error_messages['empty']) + 'message': str(error_messages['empty']) }) else: return redirect(self.get_error_url()) @@ -597,8 +597,6 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, CartMixin, TemplateView return context def dispatch(self, request, *args, **kwargs): - from pretix.base.services.cart import error_messages - err = None v = request.GET.get('voucher') @@ -652,7 +650,7 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, CartMixin, TemplateView pass if err: - messages.error(request, _(err)) + messages.error(request, str(err)) return redirect_to_url(self.get_next_url() + "?voucher_invalid") return super().dispatch(request, *args, **kwargs) diff --git a/src/pretix/presale/views/order.py b/src/pretix/presale/views/order.py index d99b245624..b1669fdb51 100644 --- a/src/pretix/presale/views/order.py +++ b/src/pretix/presale/views/order.py @@ -1389,22 +1389,16 @@ class OrderChange(EventViewMixin, OrderDetailMixin, TemplateView): selected[i, None] = val, price if sum(a[0] for a in selected.values()) > category['max_count']: - # TODO: Proper pluralization raise ValidationError( - _(error_messages['addon_max_count']), - 'addon_max_count', - { + error_messages['addon_max_count'] % { 'base': str(form['pos'].item.name), 'max': category['max_count'], 'cat': str(category['category'].name), } ) elif sum(a[0] for a in selected.values()) < category['min_count']: - # TODO: Proper pluralization raise ValidationError( - _(error_messages['addon_min_count']), - 'addon_min_count', - { + error_messages['addon_min_count'] % { 'base': str(form['pos'].item.name), 'min': category['min_count'], 'cat': str(category['category'].name), @@ -1412,9 +1406,7 @@ class OrderChange(EventViewMixin, OrderDetailMixin, TemplateView): ) elif any(sum(v[0] for k, v in selected.items() if k[0] == i) > 1 for i in category['items']) and not category['multi_allowed']: raise ValidationError( - _(error_messages['addon_no_multi']), - 'addon_no_multi', - { + error_messages['addon_no_multi'] % { 'base': str(form['pos'].item.name), 'cat': str(category['category'].name), } diff --git a/src/tests/presale/test_cart.py b/src/tests/presale/test_cart.py index 12bc71cbaf..7b043d897e 100644 --- a/src/tests/presale/test_cart.py +++ b/src/tests/presale/test_cart.py @@ -1529,7 +1529,7 @@ class CartTest(CartTestMixin, TestCase): response = self.client.get('/%s/%s/redeem' % (self.orga.slug, self.event.slug), {'voucher': v.code}, follow=True) - assert error_messages['voucher_item_not_available'] in response.rendered_content + assert str(error_messages['voucher_item_not_available']) in response.rendered_content def test_voucher_price(self): with scopes_disabled():